fix(server): use kernel receive time for wall timestamps
An echo reply's receive wall timestamp now prefers the kernel arrival reading the Linux listener captured, where that reading is plausible for the datagram it describes. That removes socket-queue and runtime wakeup latency from the upstream one-way delay a client computes, and it is the only thing the observation is allowed to change. The runtime reaches the core through a crate-private entry point taking the optional observation as an explicit per-datagram argument; the public ServerCore::handle_datagram is unchanged in signature and in behavior, and both run one implementation. There is no "next receive timestamp" side channel and no clock wrapper. ClockSample stays one paired userspace instant: the preferred wall value is carried beside it on the classified echo, never inside it. Lifecycle, rate and maximum-duration policy remain on received_at.mono_ns, midpoints remain the mean of the paired userspace samples, and no monotonic instant is estimated from a wall reading. A reading that does not convert, that is later than the core's own sample, or that lags it by more than one second falls back to the userspace wall; it is never a reason to drop a datagram.
This commit is contained in:
6 files changed
+988
-32
No files matched your search
@@ -177,17 +177,74 @@ the mirror image of destination metadata, and the contrast is the point.
|
||||
on an explicit listener, or one that describes no representable instant, all
|
||||
mean the datagram is served with `kernel_rx_timestamp: None`. Only the
|
||||
destination rules a datagram out, and only for the listener that needs one.
|
||||
- **Nothing consumes it yet.** It stops at the runtime boundary. Do not thread it
|
||||
into `ServerCore`, add a timestamp argument or variant to `handle_datagram`,
|
||||
stash it in a "next receive" side channel, or wrap the core's clock. How a
|
||||
kernel-observed arrival time should enter the core is an open design question,
|
||||
to be settled deliberately and not as a side effect of some other change.
|
||||
- **Do not map wall to monotonic.** It is a realtime reading with no monotonic
|
||||
counterpart, and `ClockSample` is a paired userspace instant. Synthesizing the
|
||||
missing half would invent precision the kernel never reported.
|
||||
- **Do not judge plausibility here.** Conversion is structural only. Whether a
|
||||
representable reading is close enough to the server's own sample to measure
|
||||
against belongs with whoever measures.
|
||||
- **Do not judge plausibility in the socket layer.** Conversion there is
|
||||
structural only. Whether a representable reading is close enough to the
|
||||
server's own sample to measure against belongs with whoever measures, which is
|
||||
the core.
|
||||
|
||||
### How it reaches the core, and what it may change
|
||||
|
||||
There are two receive entry points and one implementation behind them.
|
||||
|
||||
- **`ServerCore::handle_datagram(peer, packet)` is the public one and takes no
|
||||
timing.** A caller hands over bytes and an endpoint; every instant the reply
|
||||
reports is one the core sampled itself. This is exactly the behavior it had
|
||||
before the kernel timestamp was consumed, and it is what a direct or manual
|
||||
`ServerCore` user gets.
|
||||
- **The Tokio runtime uses a crate-private entry point** that additionally takes
|
||||
the optional kernel-observed wall receive time, passed with the datagram it
|
||||
describes. That is the whole mechanism: an explicit per-datagram argument. Do
|
||||
**not** replace it with a "next receive timestamp" setter, a pending-metadata
|
||||
field, thread-local or global state, or a `ClockSource` wrapper that returns
|
||||
transport metadata on the next sample — those hide a per-datagram input behind
|
||||
ambient mutable state. Do not make the runtime path a second public API, and
|
||||
do not pass `ReplySource` or the whole `ReceivedDatagram` into the core: it
|
||||
needs a peer, bytes and an arrival observation, not socket addressing.
|
||||
- **`ClockSample` is unchanged and still one paired userspace instant.** The
|
||||
preferred receive wall value is carried beside it on the classified echo, never
|
||||
inside it. A `ClockSample` whose `wall_ns` came from the kernel and whose
|
||||
`mono_ns` came from userspace would claim two physical instants were one
|
||||
sample.
|
||||
|
||||
The acceptance rule is the client's, in principle and in its bound, and lives in
|
||||
the core as `preferred_receive_wall_ns`. A reading is used only when it converts
|
||||
into the wire's signed-nanosecond vocabulary, is not later than the core's own
|
||||
paired receive sample, and lags it by no more than `MAX_KERNEL_RX_LAG` — one
|
||||
second, inclusive, and a sanity guard rather than an expected wakeup latency.
|
||||
Anything else falls back to the userspace wall reading. **A rejected reading is
|
||||
never a rejected datagram**: no error reply, no dropped packet, no session state
|
||||
moved. Keep the two implementations separate from the client's; a shared helper
|
||||
is not worth a crate dependency for a dozen lines of timing policy.
|
||||
|
||||
What the observation may change is one field:
|
||||
|
||||
- **Measurement policy**: `recv_wall` under `StampAt::Receive` and `StampAt::Both`
|
||||
on a clock that reports wall time. Nothing else.
|
||||
- **Lifecycle policy**: always `received_at.mono_ns`, the core's own userspace
|
||||
monotonic reading. Idle expiry, rate allowance and the maximum-duration
|
||||
deadline never see a kernel value, including for the rate-limited echo that
|
||||
produces no reply and for the final echo that carries the close.
|
||||
- **Midpoint**: always the paired userspace receive and send samples. Moving only
|
||||
the wall receive endpoint would improve apparent upstream delay by exactly what
|
||||
it cost apparent downstream delay, and leave `midpoint_wall` and
|
||||
`midpoint_mono` describing different midpoints — with no residency interval
|
||||
recoverable from a midpoint to show for it.
|
||||
- **`StampAt::Send`, `StampAt::None` and `Clock::Monotonic`** emit no arrival wall
|
||||
field, so an observation cannot reach them. An open or a close ignores it too.
|
||||
- **Ordering still holds.** The reported arrival wall value is held back to the
|
||||
send reading in its own right, because it may not have come from the receive
|
||||
sample `ClockSample::not_after` orders.
|
||||
|
||||
One consequence is intended: under `Clock::Wall` with `StampAt::Both`, the
|
||||
server-processing interval a client computes now runs from the kernel's arrival
|
||||
reading rather than the server's wakeup, so it includes the kernel-to-userspace
|
||||
portion of the server's residency and the adjusted round-trip shrinks to match.
|
||||
Under `Clock::Both` the monotonic pair still yields the unchanged userspace
|
||||
interval. Do not force the two to agree, and in particular do not estimate a
|
||||
kernel monotonic instant to do it.
|
||||
- Linux only, and receive only. No macOS, FreeBSD or Windows implementation, and
|
||||
no transmit timestamping, without a reason and agreement.
|
||||
- Control-buffer capacity is derived from the cmsg payload types and asserted at
|
||||
@@ -283,7 +340,9 @@ effective parameters must be safe for the server to run.
|
||||
clock source and is stable for its life.
|
||||
- A reply's receive instant is held back to its send instant where the wall
|
||||
clock stepped backwards between the two readings, so the required ordering
|
||||
holds. That correction is per reply and stateless on purpose: latching a
|
||||
holds — and so is a reported arrival wall value that came from the kernel
|
||||
instead, which needs the rule applied in its own right because it was not part
|
||||
of the pair. That correction is per reply and stateless on purpose: latching a
|
||||
pre-step wall value across packets would keep reporting a time the host has
|
||||
corrected away, which is the smoothing the specification forbids, and
|
||||
anchoring the wall clock to the monotonic origin would drift and never pick up
|
||||
|
||||
Reference in new issue
Block a user