From cb46bd0b5fefb406875eeb876bbe666c8c3d9de3 Mon Sep 17 00:00:00 2001 From: Mahima Singh Date: Tue, 8 Sep 2026 15:14:07 +0530 Subject: [PATCH 1/6] Fix race condition destroying completed ondemand VM requests ABS-issued multi-VM ondemand requests were occasionally being marked 'failed' and having their VMs destroyed even when all requested VMs had already finished cloning. Root cause was two independent, racing code paths with no shared lock: - the ondemand_provisioner thread's periodic sweep (check_ondemand_requests_ready -> request_expired?), and - the per-VM pending-check thread (_check_pending_vm -> move_pending_vm_to_ready), spawned fresh per VM per check cycle. Both read/write the same vmpooler__odrequest__ status and the vmpooler__provisioning__processing zset with only a per-VM mutex (vm_mutex) protecting VM state, not the request as a whole. This fixes it three ways: 1. In check_ondemand_request_ready, check vms_ready? before request_expired?, so a request that just became fully provisioned is marked ready instead of being killed by a same-tick expiry check. 2. Guard request_expired? against a nil score. redis.zscore returns nil once an entry is removed from the processing set; nil.to_i is 0, which previously produced a multi-decade "elapsed" delta that trivially exceeded any configured ondemand_request_ttl. 3. Add a per-request_id mutex (request_mutex, mirroring the existing vm_mutex pattern) around the critical sections in both check_ondemand_request_ready and move_pending_vm_to_ready, so the expiry sweep and a VM's completion callback can no longer interleave on the same request. --- lib/vmpooler/pool_manager.rb | 85 ++++++++++++++++++++++------------ spec/unit/pool_manager_spec.rb | 27 ++++++++++- 2 files changed, 81 insertions(+), 31 deletions(-) diff --git a/lib/vmpooler/pool_manager.rb b/lib/vmpooler/pool_manager.rb index 0580d332..66835953 100644 --- a/lib/vmpooler/pool_manager.rb +++ b/lib/vmpooler/pool_manager.rb @@ -37,6 +37,7 @@ def initialize(config, logger, redis_connection_pool, metrics) @reconfigure_pool = Concurrent::Hash.new @vm_mutex = Concurrent::Hash.new + @request_mutex = Concurrent::Hash.new # Name generator for generating host names @name_generator = Spicy::Proton.new @@ -242,29 +243,31 @@ def move_pending_vm_to_ready(vm, pool, redis, request_id = nil) finish = format('%