feat(managed): add unified Tokio managed task #49

Merged
Lochnair merged 5 commits from feat/tokio-managed-task into main 2026-08-04 17:19:18 +00:00
Lochnair commented 2026-08-04 14:59:18 +00:00 (Migrated from github.com)

Summary

Introduces the first functional unified Tokio managed client driver.

  • Adds irtt_client::managed as the permanent new namespace.
  • Adds one authoritative ManagedClientTask owning zero or more per-target AsyncClients.
  • Supports static multi-target operation, explicit-stop empty controllers, burst/staggered pacing, graceful drain/close, per-target failure isolation, durable outcomes, and task-drop abandonment.
  • Keeps the existing root managed implementation as temporary legacy scaffolding for CLI/TUI compatibility.
  • Extracts shared TargetId so legacy and new managed APIs use the same type.

Testing

  • Adds exactly 20 focused managed lifecycle tests (626 lines).
  • Passes formatting, Tokio/all-feature checks and strict Clippy.
  • Passes no-default, Tokio, client all-feature, and workspace all-feature tests.
  • Focused managed suite passed 10 consecutive repetitions in both the development and detached final-HEAD worktrees.
  • Passes Rust 1.85 metadata, declared Rust 1.88 check, and Linux/Windows cross-target checks.

Scope

No CLI, TUI, protocol, stats, manifest, lockfile, documentation, workflow, push, or PR changes.

## Summary Introduces the first functional unified Tokio managed client driver. - Adds `irtt_client::managed` as the permanent new namespace. - Adds one authoritative `ManagedClientTask` owning zero or more per-target `AsyncClient`s. - Supports static multi-target operation, explicit-stop empty controllers, burst/staggered pacing, graceful drain/close, per-target failure isolation, durable outcomes, and task-drop abandonment. - Keeps the existing root managed implementation as temporary legacy scaffolding for CLI/TUI compatibility. - Extracts shared `TargetId` so legacy and new managed APIs use the same type. ## Testing - Adds exactly 20 focused managed lifecycle tests (626 lines). - Passes formatting, Tokio/all-feature checks and strict Clippy. - Passes no-default, Tokio, client all-feature, and workspace all-feature tests. - Focused managed suite passed 10 consecutive repetitions in both the development and detached final-HEAD worktrees. - Passes Rust 1.85 metadata, declared Rust 1.88 check, and Linux/Windows cross-target checks. ## Scope No CLI, TUI, protocol, stats, manifest, lockfile, documentation, workflow, push, or PR changes.
chatgpt-codex-connector[bot] (Migrated from github.com) reviewed 2026-08-04 15:07:24 +00:00
chatgpt-codex-connector[bot] (Migrated from github.com) left a comment

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1cb6e07706

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

### 💡 Codex Review Here are some automated review suggestions for this pull request. **Reviewed commit:** `1cb6e07706` <details> <summary>ℹ️ About Codex in GitHub</summary> <br/> [Your team has set up Codex to review pull requests in this repo](https://chatgpt.com/codex/cloud/settings/general). Reviews are triggered when you - Open a pull request for review - Mark a draft as ready - Comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". </details>
@@ -0,0 +1,1561 @@
use std::{
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 15:07:24 +00:00

P2 Badge Drain all queued datagrams before closing

When several replies are already queued as the drain deadline expires, poll_recv processes only one datagram, after which this unconditional transition to Closing drops every remaining queued reply with the socket. High-rate or burst tests can therefore omit valid reply events and undercount replies_received, late, or duplicates even when those packets arrived within the configured drain window; keep polling receives until the socket reports pending/WouldBlock before closing at the deadline.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Drain all queued datagrams before closing** When several replies are already queued as the drain deadline expires, `poll_recv` processes only one datagram, after which this unconditional transition to `Closing` drops every remaining queued reply with the socket. High-rate or burst tests can therefore omit valid reply events and undercount `replies_received`, `late`, or `duplicates` even when those packets arrived within the configured drain window; keep polling receives until the socket reports pending/WouldBlock before closing at the deadline. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 15:07:24 +00:00

P1 Badge Pace against the negotiated probe interval

When loose negotiation accepts an IntervalReduced response, this gate continues using the originally configured interval rather than the interval stored in each target's negotiated schedule. In staggered mode—even with only one target—a request for a 1-second interval negotiated down to 100 ms keeps the gate closed for roughly 1 second after each send, causing skip_missed_probe_slots_at to discard most negotiated probe slots and silently produce an incorrect experiment rate; derive the gate spacing from the active targets' negotiated intervals.

Useful? React with 👍 / 👎.

**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange?style=flat)</sub></sub> Pace against the negotiated probe interval** When loose negotiation accepts an `IntervalReduced` response, this gate continues using the originally configured interval rather than the interval stored in each target's negotiated schedule. In staggered mode—even with only one target—a request for a 1-second interval negotiated down to 100 ms keeps the gate closed for roughly 1 second after each send, causing `skip_missed_probe_slots_at` to discard most negotiated probe slots and silently produce an incorrect experiment rate; derive the gate spacing from the active targets' negotiated intervals. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 15:07:24 +00:00

P2 Badge Preserve the primary outcome when drain receives fail

Once a target enters Draining, primary_end already records a successful test completion or requested stop, but a socket error surfaced by poll_recv here replaces that result with Failed(Receiving). For example, a connected UDP socket can report a late ICMP error while performing this optional cleanup, making an otherwise completed experiment increment failed_target_outcomes; preserve primary_end and record this as cleanup_failure, as the subsequent closing state already does for close errors.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Preserve the primary outcome when drain receives fail** Once a target enters `Draining`, `primary_end` already records a successful test completion or requested stop, but a socket error surfaced by `poll_recv` here replaces that result with `Failed(Receiving)`. For example, a connected UDP socket can report a late ICMP error while performing this optional cleanup, making an otherwise completed experiment increment `failed_target_outcomes`; preserve `primary_end` and record this as `cleanup_failure`, as the subsequent closing state already does for close errors. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) reviewed 2026-08-04 15:45:33 +00:00
chatgpt-codex-connector[bot] (Migrated from github.com) left a comment

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1af8afcfae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

### 💡 Codex Review Here are some automated review suggestions for this pull request. **Reviewed commit:** `1af8afcfae` <details> <summary>ℹ️ About Codex in GitHub</summary> <br/> [Your team has set up Codex to review pull requests in this repo](https://chatgpt.com/codex/cloud/settings/general). Reviews are triggered when you - Open a pull request for review - Mark a draft as ready - Comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". </details>
@@ -0,0 +1,1561 @@
use std::{
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 15:45:33 +00:00

P2 Badge Reject unschedulable final-drain durations at construction

When final_drain is large enough that latest.checked_add(final_drain) overflows (for example, Duration::MAX), construction accepts the configuration but this path replaces the already-determined TestComplete or Stopped result with a target failure. That incorrectly increments failed_target_outcomes after an otherwise successful experiment; validate that the drain duration is schedulable in ManagedClient::task, or preserve primary_end and report the overflow as a cleanup failure.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Reject unschedulable final-drain durations at construction** When `final_drain` is large enough that `latest.checked_add(final_drain)` overflows (for example, `Duration::MAX`), construction accepts the configuration but this path replaces the already-determined `TestComplete` or `Stopped` result with a target failure. That incorrectly increments `failed_target_outcomes` after an otherwise successful experiment; validate that the drain duration is schedulable in `ManagedClient::task`, or preserve `primary_end` and report the overflow as a cleanup failure. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 15:45:33 +00:00

P2 Badge Finish in-flight opening cleanup before stopping

When graceful stop is requested after an open request has been submitted, this branch immediately drops both the client and AsyncOpenState. If the server has already allocated a session—or poll_open has retained a cleanup-close packet after rejecting a trusted reply—the server never receives a close and must retain that session until its own timeout. Continue driving the open transaction during stop so retained cleanup completes, and close normally if opening succeeds.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Finish in-flight opening cleanup before stopping** When graceful stop is requested after an open request has been submitted, this branch immediately drops both the client and `AsyncOpenState`. If the server has already allocated a session—or `poll_open` has retained a cleanup-close packet after rejecting a trusted reply—the server never receives a close and must retain that session until its own timeout. Continue driving the open transaction during stop so retained cleanup completes, and close normally if opening succeeds. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 15:45:33 +00:00

P1 Badge Reset the stagger gate when a target becomes active

When this target opens while another target's existing stagger gate is still active, the gate retains the spacing computed for the old active set. For example, one 1-second target can send while it is the sole active target and set a 1-second gate; a second target that opens milliseconds later cannot send its initial probe until that gate expires, so a sub-second run sends no probes at all and longer runs skip multiple slots. Reset or clamp send_gate using the newly active membership when installing this state.

Useful? React with 👍 / 👎.

**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange?style=flat)</sub></sub> Reset the stagger gate when a target becomes active** When this target opens while another target's existing stagger gate is still active, the gate retains the spacing computed for the old active set. For example, one 1-second target can send while it is the sole active target and set a 1-second gate; a second target that opens milliseconds later cannot send its initial probe until that gate expires, so a sub-second run sends no probes at all and longer runs skip multiple slots. Reset or clamp `send_gate` using the newly active membership when installing this state. Useful? React with 👍 / 👎.
@@ -0,0 +671,4 @@
}
}
}
let received = match client.poll_recv(cx) {
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 15:45:33 +00:00

P1 Badge Expire overdue probes before matching queued replies

When the Tokio task is not polled across a probe timeout and a reply arrives during that stall, both the socket and timeout are ready on the next poll, but this receive runs before the overdue poll_timeouts_at call below. The reply is therefore removed from pending and reported as an on-time EchoReply, suppressing both the expected EchoLoss and LateReply and corrupting loss/latency results under executor load. Process deadlines at now before receiving queued datagrams.

Useful? React with 👍 / 👎.

**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange?style=flat)</sub></sub> Expire overdue probes before matching queued replies** When the Tokio task is not polled across a probe timeout and a reply arrives during that stall, both the socket and timeout are ready on the next poll, but this receive runs before the overdue `poll_timeouts_at` call below. The reply is therefore removed from `pending` and reported as an on-time `EchoReply`, suppressing both the expected `EchoLoss` and `LateReply` and corrupting loss/latency results under executor load. Process deadlines at `now` before receiving queued datagrams. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) reviewed 2026-08-04 16:36:29 +00:00
chatgpt-codex-connector[bot] (Migrated from github.com) left a comment

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8445c0cd99

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

### 💡 Codex Review Here are some automated review suggestions for this pull request. **Reviewed commit:** `8445c0cd99` <details> <summary>ℹ️ About Codex in GitHub</summary> <br/> [Your team has set up Codex to review pull requests in this repo](https://chatgpt.com/codex/cloud/settings/general). Reviews are triggered when you - Open a pull request for review - Mark a draft as ready - Comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". </details>
@@ -0,0 +1,1561 @@
use std::{
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 16:36:30 +00:00

P1 Badge Avoid lengthening the gate when an active target exits

When a target leaves the active set, this recomputation can replace an expired short gate with last_stagger_send + the remaining target's much longer interval. For example, if a 100 ms target finishes after its last send at 900 ms while a later-opened 1-second target is due at 1.05 seconds, removing the first target moves the gate to 1.9 seconds; the remaining target then misses its due probe entirely if its negotiated run ends before that gate. Preserve the existing gate on removals, or otherwise ensure membership changes cannot postpone a target beyond its own next-send deadline.

Useful? React with 👍 / 👎.

**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange?style=flat)</sub></sub> Avoid lengthening the gate when an active target exits** When a target leaves the active set, this recomputation can replace an expired short gate with `last_stagger_send +` the remaining target's much longer interval. For example, if a 100 ms target finishes after its last send at 900 ms while a later-opened 1-second target is due at 1.05 seconds, removing the first target moves the gate to 1.9 seconds; the remaining target then misses its due probe entirely if its negotiated run ends before that gate. Preserve the existing gate on removals, or otherwise ensure membership changes cannot postpone a target beyond its own next-send deadline. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 16:36:30 +00:00

P2 Badge Exclude already-replied probes from the drain deadline

Because latest_committed_timeout records every accepted probe even after its reply removed it from pending, a successful finite run waits until that obsolete timeout plus final_drain before closing. With the defaults, an immediate reply to the last probe sent around 2 seconds leaves a timeout around 6 seconds, so a nominal 3-second test does not return until roughly 6.1 seconds; larger configured probe timeouts make the delay arbitrarily worse. Base this deadline on currently pending or timed-out probes rather than the historical last committed timeout.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Exclude already-replied probes from the drain deadline** Because `latest_committed_timeout` records every accepted probe even after its reply removed it from `pending`, a successful finite run waits until that obsolete timeout plus `final_drain` before closing. With the defaults, an immediate reply to the last probe sent around 2 seconds leaves a timeout around 6 seconds, so a nominal 3-second test does not return until roughly 6.1 seconds; larger configured probe timeouts make the delay arbitrarily worse. Base this deadline on currently pending or timed-out probes rather than the historical last committed timeout. Useful? React with 👍 / 👎.
@@ -0,0 +1099,4 @@
now,
OpenSessionFailureCleanup::Drain,
);
SendResult::Failed { accepted }
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 16:36:30 +00:00

P2 Badge Close active sessions after send failures

When an open target encounters a send-side error, this branch immediately marks it terminal and drops the AsyncClient without sending its prepared close packet. This occurs through ordinary configuration rather than only transport failure—for example, with max_pending_probes = 1 and an interval shorter than probe_timeout, the second send returns PendingLimitExceeded while the first probe remains outstanding—so the server retains an allocated session until its own timeout and the outstanding probe is never drained. Preserve the classified failure as the primary end reason while driving the target through drain/close cleanup.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Close active sessions after send failures** When an open target encounters a send-side error, this branch immediately marks it terminal and drops the `AsyncClient` without sending its prepared close packet. This occurs through ordinary configuration rather than only transport failure—for example, with `max_pending_probes = 1` and an interval shorter than `probe_timeout`, the second send returns `PendingLimitExceeded` while the first probe remains outstanding—so the server retains an allocated session until its own timeout and the outstanding probe is never drained. Preserve the classified failure as the primary end reason while driving the target through drain/close cleanup. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] commented 2026-08-04 17:09:57 +00:00 (Migrated from github.com)

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

You have reached your Codex usage limits for code reviews. You can see your limits in the [Codex usage dashboard](https://chatgpt.com/codex/cloud/settings/usage). To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your [settings](https://chatgpt.com/codex/cloud/settings/code-review).
Sign in to join this conversation.