Skip to content

Mutation testing: client pool lifecycle and slot rotation survivors #593

Description

@leynos

Context

From the first complete full mutation-testing run (ADR-007 shared workflow, run 29074771985, 2026-07-10, main @ 94a5d44). The client pool's shutdown and rotation logic can be mutated without any test noticing.

Surviving mutants

  • src/client/pool/client_pool.rs:149replace ClientPoolInner::is_shutdown -> bool with false
  • src/client/pool/client_pool.rs:187replace > with == in ClientPoolInner::ordered_slots
  • src/client/pool/scheduler.rs:210replace PoolScheduler::notify_shutdown with ()
  • src/client/pool/scheduler.rs:101replace has_waiters -> bool with false / replace || with &&
  • src/client/send_streaming.rs:92replace SendStreamingConfig::chunk_size -> Option<usize> with None / Some(0) / Some(1)

Analysis

No test closes a pool and then attempts an acquire, so is_shutdown can be forced to false and both try_acquire_immediately and acquire_for_handle proceed as if the pool were live. Every pool test uses pool_size(1), where rotate_left(0) is a no-op, so round-robin slot rotation is never observed. notify_shutdown drains queued waiters with disconnected(); nothing blocks a waiter across a shutdown, so an empty body survives (such waiters would hang). The public SendStreamingConfig::chunk_size getter has zero callers (internal code reads the field directly), so all three accessor mutants survive — note issue #570's remark that the neighbouring chunk-size mutants were caught applies to the streaming path, not this getter.

Suggested tests

  1. pool.close().await then handle.acquire().await — assert ClientError::disconnected() is returned promptly (kills is_shutdown).
  2. Build a pool with pool_size(2) and max_in_flight_per_socket(1); acquire and drop leases repeatedly and assert the test server observes two established connections (kills the ordered_slots rotation mutant, which leaves every acquire on slot 0).
  3. Saturate the single slot, spawn a blocked handle.acquire(), close the pool, and assert the blocked acquire resolves to Err(disconnected) within a bounded timeout (kills notify_shutdown).
  4. Assert SendStreamingConfig::default().with_chunk_size(4096).chunk_size() == Some(4096) and that the default is None (kills the accessor mutants).

Accepted

scheduler.rs:101 (has_waiters, both variants) is a private race-recovery guard distinguishable only under an adversarial interleaving (waiter enqueued between take_next_waiter and the losing kick CAS). Example-based tests cannot reach it deterministically; a loom model of the scheduler would be the appropriate adversary. Accepted for now.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingconcurrencymediumCould be disruptive, but might not happentesting

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions