Skip to content

perf: replace threads 100ms polling loop with poison-pill shutdown #157

@ElFantasma

Description

@ElFantasma

Parent issue

Part of #156 (performance audit findings).

Problem

threads/actor.rs uses rx.recv_timeout(Duration::from_millis(100)) in the message loop to periodically check cancellation_token.is_cancelled():

match rx.recv_timeout(Duration::from_millis(100)) {
    Ok(mut envelope) => { /* handle message */ }
    Err(RecvTimeoutError::Timeout) => {
        if cancellation_token.is_cancelled() { break; }
    }
    Err(RecvTimeoutError::Disconnected) => break,
}

This causes:

  • Up to 100ms shutdown latency after stop() is called
  • 10 wakeups/sec per idle actor (constant CPU churn)
  • Unnecessary overhead for applications with many idle actors

Proposed fix

Replace the timeout-based polling with a poison-pill approach:

  1. Wrap mailbox messages: enum MailboxItem<A> { Message(Box<dyn Envelope<A>>), Shutdown }
  2. When stop() is called, send MailboxItem::Shutdown through the channel
  3. Replace recv_timeout with blocking rx.recv()
  4. On Shutdown, break the loop — instant cancellation, zero polling

This keeps CancellationToken for timers/streams but removes the polling loop from the core message path.

Files to change

  • concurrency/src/threads/actor.rs — message loop, stop() impl
  • Possibly rt/src/threads/mod.rs — if channel type changes

Testing

  • Existing thread actor tests should pass unchanged
  • Add test verifying shutdown latency < 10ms
  • Add test verifying idle actor has no CPU wakeups

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions