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
//! What:
//! The service's side of one waiting client: the queue of messages the landing thread answers with, and the loop that writes
//! them to the client's connection together with a heartbeat while nothing else is to say.
//! Why:
//! A client waits for its answer with no clock of its own (`service_client.rs`), so the only thing that ends its wait is
//! silence. The thread that read the request has nothing else to do while the request is queued or landing, so it writes
//! the heartbeats, and a long batch, a long queue or a slow disk in front of the landing never reads as a dead service. The
//! landing thread hands its messages to that same thread through a channel instead of writing the connection itself, so the
//! connection has one writer, a line is written whole, and no lock guards it.
//!
//! In TS you'd write (pseudocode):
//! ```ts
//! const [reply, messages] = makeReply(); queue.push({ request, reply }); await writeUntilAnswered(socket, messages, HEARTBEAT_EVERY);
//! ```
use crate;
use Write;
use UnixStream;
use ;
use Duration;
/// What:
/// The landing thread's end of one client's messages.
/// Why:
/// Dropping it is what answering the request ends with: the writer sees the channel closed, writes what is left, and closes
/// the connection.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// class Reply { constructor(private queue: Channel<ServiceMessage>) {} send(message) { this.queue.push(message) } }
/// ```
/// Writes `message` and a newline to `stream` as one write; false when the client is gone.
/// What:
/// Writes each queued message to the client, and a heartbeat whenever `every` passes with nothing queued, until the reply is
/// dropped or the client is gone.
/// Why:
/// The heartbeat is the proof of life the client's wait rests on; the queued messages come first, in order, so a beat never
/// stands between a notice and the answer that follows it.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// for (;;) { const m = await messages.next({ timeout: every }); if (m === END) return; if (!write(m ?? heartbeat)) return; }
/// ```
/// What:
/// Where a connection's beat asks for a message to write before the beat.
/// A `trait` is a named set of methods that different types can each supply, like a TypeScript `interface`.
/// Why:
/// The beat asks at most once; what it asks (the board, the request's identity, the last words said) is the state of a small
/// struct that implements this.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// interface NoteSource { note(): ServiceMessage | undefined }
/// ```
/// What:
/// A note source that never has a note.
/// Why:
/// `write_until_answered` is `write_until_answered_noting` with nothing to add before each beat.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// const nothingToSay = { note: (): ServiceMessage | undefined => undefined };
/// ```
;
/// What:
/// `write_until_answered`, where `note` is asked for a message whenever the beat is due, and what it returns is written before the
/// beat.
/// Why:
/// A client queued behind a write that never ends is told what the service is stuck in (`service_busy.rs`); the beat is the
/// one moment this thread has nothing queued to write, so the notice rides on it and is asked for at most once a beat.
///
/// In TS you'd write (pseudocode):
/// ```ts
/// for (;;) { const m = await messages.next({ timeout: every }); if (m === END) return; if (m === undefined) { const n = note(); if (n && !write(n)) return; } if (!write(m ?? heartbeat)) return; }
/// ```
/// The reply and its beat are controlled in the test module.