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
//! Live subscriptions: the half of the protocol that arrives unasked.
//!
//! A subscription is a cursor and a loop. It reads forward from where the
//! consumer last was, sends what it finds, and then waits for a reason to look
//! again. Everything interesting is in what counts as a reason, and in what
//! happens when the consumer stops keeping up.
//!
//! # Two wake-ups, because one of them is allowed to fail
//!
//! The append path already queues a `pg_notify` INSIDE its transaction, so a
//! wake-up is delivered only if the append committed and never announces one
//! that rolled back. That is the fast path and it is the normal path.
//!
//! It is not a guarantee. A notification can be lost — a dropped listener
//! connection, a backend restart, a listener that had not reconnected yet — and
//! PostgreSQL will not redeliver it. On a busy log that heals itself, because
//! the next append wakes everyone anyway. On a log that goes quiet immediately
//! after, it does not heal at all: the consumer waits for an append that has
//! already happened, forever. So every subscription ALSO re-reads on
//! [`SUBSCRIPTION_POLL_SECS`], which turns the worst case from unbounded into a
//! number.
//!
//! The wake-up is a [`tokio::sync::watch`] rather than a notify, and that
//! choice does real work: `changed()` is edge-triggered against a value the
//! receiver has not yet observed, so an append landing BETWEEN a read and the
//! wait is still pending when the wait begins. A primitive that only wakes
//! current waiters would drop exactly that one, and it is the likeliest race
//! here — the window is the width of a database round trip.
//!
//! # A slow consumer loses its subscription, not its connection
//!
//! Delivery is at-least-once and the cursor is what makes that safe: a consumer
//! that reconnects from its last delivered sequence misses nothing and may see
//! a repeat. When a consumer stops reading, the queue fills and the send blocks;
//! after [`SLOW_CONSUMER_TIMEOUT_SECS`] the subscription gives up and says so
//! with the cursor the consumer ACTUALLY received — not the one the kernel had
//! reached — so resuming from it has no gap. The connection stays open and its
//! other work carries on.
//!
//! "Actually received" is why a batch travels with a progress cell rather than
//! alone. Queuing a batch is not delivering it: the write loop buffers up to
//! `BATCH_QUEUE_DEPTH` of them, so the cursor the kernel has READ to can be
//! thousands of events past the last frame that reached the socket — and a
//! client resuming from that number would skip every event in between. The cell
//! is written by the loop that owns the write half, after the frame is out, so
//! the number this subscription closes with is one the client has seen. It can
//! lag a frame that is being written as the timeout fires; lagging repeats an
//! event, which at-least-once allows, where leading loses one.
use Arc;
use ;
use Duration;
use ;
use EventStore;
use ;
use PgPool;
use PgListener;
use ;
use ;
use crate;
/// Events read per catch-up page.
///
/// A subscription that is far behind walks forward a page at a time rather than
/// asking for everything at once; the byte cut in [`fit_page`] then trims that
/// page to what one frame can carry.
const SUBSCRIPTION_BATCH_ROWS: usize = 256;
/// Turn database notifications into wake-ups, until the daemon stops.
///
/// The published value is a counter, not a watermark: subscriptions re-read
/// from their own cursors anyway, so all this has to carry is "something
/// changed". A counter is always different from the last one, which means a
/// receiver can never mistake two appends for one.
///
/// A listener that cannot connect or that dies is not fatal, and deliberately
/// so — it degrades a subscription from milliseconds to
/// [`SUBSCRIPTION_POLL_SECS`], and a kernel that refused to serve at all
/// because its notification channel was unavailable would be trading a slower
/// stream for no stream.
pub async
/// Why a subscription stopped, when it stopped for a reason worth reporting.
///
/// The reason travels as a code and nothing more, because `StreamClosed` carries
/// no message: what a client does about a stream that ended is resume from the
/// cursor, and the two codes that reach it — a slow consumer and a failed read —
/// are the whole of what it can branch on.
/// Deliver from `cursor` forward until the consumer stops keeping up or the
/// connection ends.
///
/// `batches` is the queue the consumer drains and `responses` is the priority
/// one, which is why a `StreamClosed` can still be delivered to a consumer
/// whose batch queue is precisely the thing that is full.
///
/// `delivered` starts at the cursor this subscription was opened with, so a
/// stream that ends having sent nothing reports where it began rather than the
/// beginning of the log.
pub async