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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
//! How a connection is routed, followed for as long as it lives.
//!
//! iroh establishes over a relay and hole-punches to a direct path a moment
//! later, so the path a connection has when it is accepted is routinely not
//! the path it spends its life on. Both sides used to read it exactly once,
//! at that moment, and never again: a session that upgraded reported
//! `relayed` until it ended, and one that degraded went on reporting
//! `direct`. That leaves the status unable to answer the only question it
//! exists for — *is hole punching working from here* — which is why this
//! module is a watcher and not a second copy of the reading.
//!
//! **A poll rather than iroh's event stream, and the choice was costed
//! rather than assumed.** [`Connection::path_events`] is the event-shaped
//! API and looks like the obvious pick, but iroh re-exports neither the
//! `Stream` trait nor `n0_future`, so calling `next` on it means taking a
//! direct dependency on one of `futures-core` / `futures-lite` /
//! `tokio-stream` / `n0-future` to keep a status line honest.
//! [`Connection::paths`] needs none of them, and is the call the single
//! reading already made. The second reason is what settles it: a
//! `PathEvent` carries no round-trip time, and the RTT is the half of a
//! reading that makes it a measurement rather than a label — so an
//! event-driven watcher would have had to poll for the number anyway, and
//! would have been both mechanisms instead of one.
//!
//! (Not `paths_stream` either, for a plainer reason: it borrows the
//! `Connection` and yields values iroh documents as unable to cross a task
//! boundary.)
//!
//! **Correcting the record.** PR #50's description — the page a reader
//! following the changelog's link for this change arrives at — says the
//! watcher is "a per-connection watcher on `Connection::path_events()`" and
//! that `paths_stream` was the alternative it beat. It is neither of those:
//! the reading is the [`Connection::paths`] poll below, for the two reasons
//! above it. The commit body that landed on `main` describes the poll
//! correctly, so the PR page is the only copy that is wrong — and a merged
//! description cannot be corrected in place, which is why the correction is
//! here, where that reader arrives next.
//!
//! [`Connection::paths`]: iroh::endpoint::Connection::paths
//! [`Connection::path_events`]: iroh::endpoint::Connection::path_events
use Duration;
use TransportAddr;
use ;
use crate;
/// How often the selected path is read again.
///
/// A status line's resolution, not a measurement's. Nothing in this crate
/// decides anything on the value — it is printed by the CLI and rendered by
/// an embedder — so a change is worth knowing about within a second and is
/// not worth a wakeup more often than that. The cost of the read is one
/// mutex and a QUIC statistics copy per connection.
const CADENCE: Duration = from_secs;
/// How a connection is reaching the peer, and what that path costs.
///
/// Shared by both sides, which ask the identical question of the identical
/// type. The rule was written twice before it moved here, and two copies of
/// a rule about what counts as `Direct` is one copy too many for a value the
/// CLI prints and an embedder watches.
pub
/// What to publish when a reading comes back with no selected path: the
/// previous one, unchanged.
///
/// [`Reading::PENDING`] is `Relayed`, which is the right answer for a
/// connection that has established nothing yet and the wrong one for a live
/// connection between paths. iroh clears its selection the moment the
/// selected path is abandoned and sets it again when the replacement is
/// chosen, so a poll landing in that window reads `PENDING` about a
/// connection that is not on a relay and never was.
///
/// **This is a new failure mode and not an old one**, which is why the guard
/// arrives with the watcher rather than being left to a later fix. Reading
/// once at accept could not land in that window by construction; reading
/// every second can, and each landing would publish `Direct → Relayed →
/// Direct` — three transitions an embedder watching `status_changed()`
/// cannot tell from two real migrations.
///
/// Holding the last reading, RTT included, rather than inventing a third
/// state is the same call [`Reading::PENDING`] itself makes and for the same
/// reason: the public [`PipeStatus`](crate::PipeStatus) has `Idle` for "no
/// peer" and deliberately nothing for "a peer mid-migration". What is held
/// is a measurement that was true a second ago, which is what every reading
/// here is; what would be published instead is a path the connection is not
/// on.
const
/// Read how `connection` is routed right now.
///
/// A snapshot, honest about the moment it was taken — which is the whole
/// reason [`follow`] exists to take it again.
pub
/// The rule: the relay is what this crate can name, so it is what is tested
/// for, and everything else counts as direct.
///
/// Not the inverse test on `is_ip`, and `TransportAddr` being
/// `#[non_exhaustive]` is why the difference matters. The distinction being
/// drawn is *through the relay or not*, which is what explains latency and
/// what the README promises; a transport iroh adds later is not the relay
/// and should not be reported as if it were.
/// A duration as whole milliseconds, saturating.
///
/// `tracing` has no `u128` field and neither does [`PeerView`], so the
/// conversion happens once, here. Saturation is unreachable arithmetic
/// rather than a policy: it would take a round trip of half a billion years.
///
/// [`PeerView`]: crate::PeerView
pub
/// Follow `connection`'s path until it — or the pipe — ends, handing every
/// reading to `publish`.
///
/// Ends on its own, which is what lets a caller spawn it and forget it: the
/// connection closing and the pipe closing are both arms here. The connect
/// side selects on this beside its own copies of those two and gets the same
/// answer either way; the serve side has an accept loop to run at the same
/// time and spawns it instead.
///
/// `publish` is called on every reading rather than only on a change,
/// because the RTT moves when the path does not and a status page rendering
/// it wants the current number. It is the *status* that must not churn, and
/// that is already handled where it belongs:
/// [`Lifecycle::set_status`](crate::lifecycle::Lifecycle::set_status) drops
/// a value equal to the one in force.
pub async
/// Read again forever, at [`CADENCE`]. Never returns; [`follow`] is what
/// ends it.
///
/// Takes the reading as a closure rather than the [`Connection`] it comes
/// from, which is what lets the cadence and the [`settled`] rule be driven
/// by a scripted sequence in a test. Nothing in one process can make a real
/// path lapse and come back, and a guard nothing can exercise is a guard
/// nobody can tell has stopped working.
async