1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//! Shared io_uring teardown policy for every ring-owning thread: the
//! replication sender's live-stream ring, the replication receiver's
//! session ring, and the client-facing reader.
//!
//! All of them hand the kernel pointers into heap buffers — a landing
//! area a RECV writes into, a source buffer a SEND reads out of, a
//! provided-buffer pool a multishot RECV selects from. Closing the ring
//! fd does not retract those pointers: ring teardown is *asynchronous*,
//! so an operation still in flight can complete after the allocation
//! has been returned to the allocator. On the replication sender that
//! surfaced as malloc-metadata corruption and a SIGSEGV at thread exit;
//! being a kernel-side store, it is invisible to AddressSanitizer.
//!
//! Every side therefore tears down the same way, and shares the policy
//! here so it cannot drift apart: wake whatever the kernel holds with a
//! `shutdown(2)` (never an AsyncCancel alone — some hosts filter
//! io_uring opcodes), reap completions until it holds nothing, and only
//! then release the buffers — leaking them if the drain cannot be
//! proven complete.
use RawFd;
use ;
/// Upper bound on a teardown drain. The wake is a `shutdown(2)` on the
/// very socket the pending operations are bound to, so they are
/// expected to complete within microseconds; this bounds only the
/// pathological case (a wedged io-wq worker), where waiting forever
/// would hang the transport thread and everything that joins it.
pub const DRAIN_TIMEOUT: Duration = from_secs;
/// Poll iterations spent spinning before backing off to a sleep.
const SPIN_LIMIT: u32 = 1024;
/// Sleep between polls once [`SPIN_LIMIT`] is exhausted. Long enough
/// that a pathological drain costs nothing, short enough that it adds
/// no meaningful latency to an ordinary teardown.
const BACKOFF_SLEEP: Duration = from_micros;
/// Wake every operation the kernel is holding against `fd`: a shutdown
/// socket completes a pending RECV with EOF and a pending SEND with an
/// error, immediately and without relying on AsyncCancel opcode
/// availability — this must also work on hosts that filter io_uring
/// opcodes.
///
/// Best-effort by design, hence the discarded return: callers hold an
/// fd that outlives the ring being torn down, and a socket the peer
/// already reset simply answers `ENOTCONN`, which changes nothing about
/// the drain that follows.
pub
/// Deadline and backoff for a teardown drain loop.
///
/// Drains poll the memory-mapped completion queue rather than blocking
/// in `submit_and_wait`, which takes no timeout: an operation the wake
/// failed to rouse would hang the thread — and everything that joins it
/// — forever, and trading a use-after-free for a deadlock is not a
/// trade worth making.
pub