refactor(client): make session opening transactional #43

Merged
Lochnair merged 5 commits from refactor/transactional-opening into main 2026-08-04 05:45:58 +00:00
Lochnair commented 2026-08-04 04:05:42 +00:00 (Migrated from github.com)

Summary

Make synchronous session opening transactional for the blocking client, its
threaded managed wrapper, and the legacy managed group.

Opening transaction

SessionMachine now prepares immutable open requests, classifies received
datagrams, prepares complete active/no-test state, and commits opening
infallibly. Socket, scheduling, buffer, DSCP, retry, and cleanup ownership
remain in the adapters.

Trust and retry behavior

Each configured timeout sends exactly one request and establishes one absolute
attempt deadline. Malformed or unauthenticated traffic is ignored within that
attempt. Authenticated semantic incompatibilities remain terminal.

Adapter rollback and cleanup

Blocking and managed adapters complete fallible preparation before machine
commit. Post-token failures send a best-effort preencoded authenticated cleanup
close while preserving the primary error. DSCP and timeout rollback are
best-effort and no partial open state survives failure.

Managed receiver draining

When an opening attempt reaches its deadline, targets wait for a shared
receiver-drain generation before retrying or reporting final timeout. The
receiver completes that generation only after an empty receive observed after
the drain request, ensuring datagrams already queued in the socket are
processed first.

Multiple expired targets share one drain generation. Userspace dequeue
timestamps are no longer treated as kernel arrival times, and completed
attempts cannot be reopened by later packets.

Testing

Adds focused coverage for trust classification, HMAC behavior, absolute
deadlines, request counts, no-test opening, adapter fault injection, cleanup
close behavior, group policy failures, receiver progress, queued in-window
replies, post-deadline replies, and final timeout coordination.

Adds deterministic coverage for queued replies processed after their nominal
deadline, receiver request/receive races, shared drain generations, post-drain
reply rejection, final-timeout coordination, no-test cleanup, and no-test
group-policy separation.

## Summary Make synchronous session opening transactional for the blocking client, its threaded managed wrapper, and the legacy managed group. ## Opening transaction SessionMachine now prepares immutable open requests, classifies received datagrams, prepares complete active/no-test state, and commits opening infallibly. Socket, scheduling, buffer, DSCP, retry, and cleanup ownership remain in the adapters. ## Trust and retry behavior Each configured timeout sends exactly one request and establishes one absolute attempt deadline. Malformed or unauthenticated traffic is ignored within that attempt. Authenticated semantic incompatibilities remain terminal. ## Adapter rollback and cleanup Blocking and managed adapters complete fallible preparation before machine commit. Post-token failures send a best-effort preencoded authenticated cleanup close while preserving the primary error. DSCP and timeout rollback are best-effort and no partial open state survives failure. ## Managed receiver draining When an opening attempt reaches its deadline, targets wait for a shared receiver-drain generation before retrying or reporting final timeout. The receiver completes that generation only after an empty receive observed after the drain request, ensuring datagrams already queued in the socket are processed first. Multiple expired targets share one drain generation. Userspace dequeue timestamps are no longer treated as kernel arrival times, and completed attempts cannot be reopened by later packets. ## Testing Adds focused coverage for trust classification, HMAC behavior, absolute deadlines, request counts, no-test opening, adapter fault injection, cleanup close behavior, group policy failures, receiver progress, queued in-window replies, post-deadline replies, and final timeout coordination. Adds deterministic coverage for queued replies processed after their nominal deadline, receiver request/receive races, shared drain generations, post-drain reply rejection, final-timeout coordination, no-test cleanup, and no-test group-policy separation.
chatgpt-codex-connector[bot] (Migrated from github.com) reviewed 2026-08-04 04:11:43 +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: 29ab07c0f7

ℹ️ 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:** `29ab07c0f7` <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>
@@ -9,6 +9,26 @@ fn successful_open_handshake() {
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 04:11:44 +00:00

P2 Badge Allow platform rounding in timeout readback

On Linux in this repository, cargo test -p irtt-client fails this new assertion because the socket reports Some(76ms) after setting 75ms; receive-timeout values may be rounded to the platform timer resolution. The test already verifies the exact value passed to the restoration helper through last_restored_read_timeout, so requiring exact kernel readback makes the suite platform-dependent and should be replaced with a tolerance/range check or omitted.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Allow platform rounding in timeout readback** On Linux in this repository, `cargo test -p irtt-client` fails this new assertion because the socket reports `Some(76ms)` after setting `75ms`; receive-timeout values may be rounded to the platform timer resolution. The test already verifies the exact value passed to the restoration helper through `last_restored_read_timeout`, so requiring exact kernel readback makes the suite platform-dependent and should be replaced with a tolerance/range check or omitted. Useful? React with 👍 / 👎.
@@ -949,2 +984,4 @@
}
fn process_group_datagram(
socket: &UdpSocket,
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 04:11:44 +00:00

P1 Badge Preserve kernel arrival time for managed open windows

When the group receiver is delayed or has a socket backlog, an open reply can enter the UDP receive queue before this attempt's deadline but be dequeued afterward; received_at.mono is sampled only after recvmsg, so this check rejects that on-time reply as outside the window. The new receiver watermark uses the same userspace timestamp and therefore cannot protect this case, allowing unnecessary retries or a final OpenTimeout under scheduler delays or multi-target load; eligibility needs to use the kernel receive timestamp or another queue-draining scheme that preserves actual arrival ordering.

Useful? React with 👍 / 👎.

**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange?style=flat)</sub></sub> Preserve kernel arrival time for managed open windows** When the group receiver is delayed or has a socket backlog, an open reply can enter the UDP receive queue before this attempt's deadline but be dequeued afterward; `received_at.mono` is sampled only after `recvmsg`, so this check rejects that on-time reply as outside the window. The new receiver watermark uses the same userspace timestamp and therefore cannot protect this case, allowing unnecessary retries or a final `OpenTimeout` under scheduler delays or multi-target load; eligibility needs to use the kernel receive timestamp or another queue-draining scheme that preserves actual arrival ordering. Useful? React with 👍 / 👎.
@@ -951,0 +1077,4 @@
datagram.received_at,
datagram.meta,
);
}
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 04:11:44 +00:00

P2 Badge Skip active-session group checks for no-test replies

For a managed group using RunMode::NoTest with loose negotiation, a valid close reply that restricts the interval or DSCP now fails here with NegotiationRejected. Before this change, validate_group_negotiation ran only through the normal-open callback, and these constraints are irrelevant to no-test sessions because they never schedule probes or apply per-target DSCP; validate only normal_negotiated() replies so valid negotiation-only runs still complete.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Skip active-session group checks for no-test replies** For a managed group using `RunMode::NoTest` with loose negotiation, a valid close reply that restricts the interval or DSCP now fails here with `NegotiationRejected`. Before this change, `validate_group_negotiation` ran only through the normal-open callback, and these constraints are irrelevant to no-test sessions because they never schedule probes or apply per-target DSCP; validate only `normal_negotiated()` replies so valid negotiation-only runs still complete. Useful? React with 👍 / 👎.
@@ -121,58 +179,99 @@ impl SessionMachine {
self.config.hmac_key.is_some()
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 04:11:44 +00:00

P2 Badge Clean up tokens returned to no-test requests

When a RunMode::NoTest request receives an unexpected non-close reply with a nonzero token, this condition suppresses cleanup solely because the client is not in normal mode; prepare_open_acceptance then returns UnexpectedNoTestReply, and both the blocking and group adapters have no close packet to send. A server that allocated the returned token therefore retains an orphaned session, so cleanup should be prepared for any non-close, nonzero-token reply.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Clean up tokens returned to no-test requests** When a `RunMode::NoTest` request receives an unexpected non-close reply with a nonzero token, this condition suppresses cleanup solely because the client is not in normal mode; `prepare_open_acceptance` then returns `UnexpectedNoTestReply`, and both the blocking and group adapters have no close packet to send. A server that allocated the returned token therefore retains an orphaned session, so cleanup should be prepared for any non-close, nonzero-token reply. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) reviewed 2026-08-04 05:46:43 +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: 185e0bf3e4

ℹ️ 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:** `185e0bf3e4` <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>
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 05:46:43 +00:00

P1 Badge Bound receiver-drain waits under continuous traffic

When another active target supplies datagrams more frequently than the group's receive timeout (for example, replies at the supported 10 ms interval), recv_datagram_from keeps succeeding and this is never called. An expired opening target therefore waits forever for its drain generation at drive_open_attempts instead of retransmitting or reaching OpenTimeout, which can prevent the entire mixed group from completing. Drain completion needs an absolute bound or queue-boundary mechanism that does not require a completely idle socket.

Useful? React with 👍 / 👎.

**<sub><sub>![P1 Badge](https://img.shields.io/badge/P1-orange?style=flat)</sub></sub> Bound receiver-drain waits under continuous traffic** When another active target supplies datagrams more frequently than the group's receive timeout (for example, replies at the supported 10 ms interval), `recv_datagram_from` keeps succeeding and this is never called. An expired opening target therefore waits forever for its drain generation at `drive_open_attempts` instead of retransmitting or reaching `OpenTimeout`, which can prevent the entire mixed group from completing. Drain completion needs an absolute bound or queue-boundary mechanism that does not require a completely idle socket. Useful? React with 👍 / 👎.
chatgpt-codex-connector[bot] (Migrated from github.com) commented 2026-08-04 05:46:43 +00:00

P2 Badge Classify open-deadline overflow as configuration failure

When an accepted timeout such as Duration::MAX cannot be added to Instant::now(), this passes DurationOverflow through ManagedTargetFailure::opening; classify_target_failure then falls through to OpeningProtocol, even though no request was sent and no protocol data was processed. This mislabels the target outcome for callers and monitoring, so deadline overflow should be classified as InvalidConfiguration (or otherwise as a local failure) rather than a protocol/authentication/negotiation failure.

Useful? React with 👍 / 👎.

**<sub><sub>![P2 Badge](https://img.shields.io/badge/P2-yellow?style=flat)</sub></sub> Classify open-deadline overflow as configuration failure** When an accepted timeout such as `Duration::MAX` cannot be added to `Instant::now()`, this passes `DurationOverflow` through `ManagedTargetFailure::opening`; `classify_target_failure` then falls through to `OpeningProtocol`, even though no request was sent and no protocol data was processed. This mislabels the target outcome for callers and monitoring, so deadline overflow should be classified as `InvalidConfiguration` (or otherwise as a local failure) rather than a protocol/authentication/negotiation failure. Useful? React with 👍 / 👎.
Sign in to join this conversation.