You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Replace the pool scheduler's Arc<Mutex<SchedulerState>> plus atomic service guard and transient tokio::spawn calls with one long-lived scheduler task that owns the fairness and waiter state.
This is the final structural implementation slice for ADR 013 proposal #638. It follows the stable slot graph in #645 and should consume the scoped permit-selection API from #646.
Current behaviour
The current scheduler:
shares SchedulerState through a synchronous mutex;
uses AtomicBool::is_servicing as a distributed task-lifecycle guard;
calls kick from acquire and lease-drop paths;
spawns service_waiters when the guard transitions from idle;
may spawn a task that immediately discovers no waiter and exits;
divides shutdown, waiter selection, restart recovery, and capacity notification among several caller contexts.
This topology makes one conceptual state machine look like shared mutable data. It also complicates #550's bounded-waiter admission and leaves adversarial interleavings from #593/#535 difficult to test directly.
Proposed architecture
Create one scheduler task during pool construction. It owns SchedulerState directly and receives commands through a Tokio channel.
treat duplicate CapacityAvailable messages as harmless hints, not extra permits.
Shutdown
reject later acquires;
resolve every queued waiter with ClientError::disconnected or the chosen typed shutdown error;
cancel or drop any pending permit-selection future safely;
acknowledge explicit close() only after scheduler state has drained and the actor can terminate;
terminate on mailbox closure even when callers omit explicit close.
Public API behaviour
Preserve existing WireframeClientPool, PoolHandle, and PooledClientLease APIs where practical.
WireframeClientPool::handle() may remain synchronous by allocating a logical ID outside the actor. The implementation must prove command ordering for acquire and deregistration from one logical handle. A registration acknowledgement is acceptable if the public API change is justified, but do not quietly assume cross-sender global ordering that Tokio does not promise.
WireframeClientPool::close(self) becomes an awaitable scheduler shutdown rather than shutdown(); yield_now(); drop(self).
Drop behaviour
PooledClientLease::drop cannot await. It should:
release its OwnedSemaphorePermit by ordinary drop;
send or coalesce a non-blocking capacity hint to the actor;
tolerate an already-closed scheduler mailbox during shutdown;
avoid cloning a large ownership graph;
never spawn a task.
If permit release can be observed without a per-drop command, for example through a shared Notify polled by the actor, that is acceptable if it preserves the single-owner scheduler state and has no lost-wakeup race.
Add pool poison recovery integration coverage #539: reassess whether scheduler poison-recovery integration remains relevant once scheduler state is no longer behind std::sync::Mutex; retain slot-bookkeeping scope if still applicable.
Summary
Replace the pool scheduler's
Arc<Mutex<SchedulerState>>plus atomic service guard and transienttokio::spawncalls with one long-lived scheduler task that owns the fairness and waiter state.This is the final structural implementation slice for ADR 013 proposal #638. It follows the stable slot graph in #645 and should consume the scoped permit-selection API from #646.
Current behaviour
The current scheduler:
SchedulerStatethrough a synchronous mutex;AtomicBool::is_servicingas a distributed task-lifecycle guard;kickfrom acquire and lease-drop paths;service_waiterswhen the guard transitions from idle;This topology makes one conceptual state machine look like shared mutable data. It also complicates #550's bounded-waiter admission and leaves adversarial interleavings from #593/#535 difficult to test directly.
Proposed architecture
Create one scheduler task during pool construction. It owns
SchedulerStatedirectly and receives commands through a Tokio channel.A representative protocol is:
The exact types may differ, but the protocol and ownership must remain explicit.
Ownership split
Keep scheduler control and physical slot storage separate:
Arc<PoolCore>from Introduce PoolCore and index-based pooled leases #645 owns fixed slots and shared physical resources;SchedulerHandleowns only the command sender and synchronous handle-ID allocation if retained;SchedulerTaskowns the receiver,SchedulerState, fairness rotation, waiter counts, and shutdown state;Arc<PoolCore>, butPoolCoremust not own the command sender in a way that creates a strong cycle.Dropping the final public scheduler sender must eventually close the mailbox, terminate the task, and release the task's pool-core reference.
Responsibilities of the actor
Admission
Fairness
Capacity
CapacityAvailablemessages as harmless hints, not extra permits.Shutdown
ClientError::disconnectedor the chosen typed shutdown error;close()only after scheduler state has drained and the actor can terminate;Public API behaviour
Preserve existing
WireframeClientPool,PoolHandle, andPooledClientLeaseAPIs where practical.WireframeClientPool::handle()may remain synchronous by allocating a logical ID outside the actor. The implementation must prove command ordering for acquire and deregistration from one logical handle. A registration acknowledgement is acceptable if the public API change is justified, but do not quietly assume cross-sender global ordering that Tokio does not promise.WireframeClientPool::close(self)becomes an awaitable scheduler shutdown rather thanshutdown(); yield_now(); drop(self).Drop behaviour
PooledClientLease::dropcannot await. It should:OwnedSemaphorePermitby ordinary drop;If permit release can be observed without a per-drop command, for example through a shared
Notifypolled by the actor, that is acceptable if it preserves the single-owner scheduler state and has no lost-wakeup race.Integration with existing issues
std::sync::Mutex; retain slot-bookkeeping scope if still applicable.Acceptance criteria
SchedulerStateis owned by the actor and is not behindArc<Mutex<_>>.is_servicingatomic/spawn-restart protocol is removed.close()waits for scheduler shutdown rather than yielding speculatively.close()terminates the actor and releasesPoolCore.tokio::spawn.Tests
Add deterministic state-machine and runtime tests covering:
Use loom or a small pure scheduler-state model for event-order races that cannot be forced reliably with wall-clock Tokio tests.
Non-goals
Dependencies
References
src/client/pool/scheduler.rssrc/client/pool/client_pool.rssrc/client/pool/handle.rssrc/client/pool/lease.rs