Repository navigation
Commit f35628b
authored
Address connection races (#6867)
* Scope socket state to the connection it belongs to
The socket kept the state of its current transport in its own fields,
so asynchronous events, timers and user callbacks belonging to one
connection could act on the next one, which caused a series of races.
Wrap each transport instance in a Connection, which owns the state of
that connection: its events are only handled while it is the socket's
current connection, and it records whether we closed it, whether the
socket gave up on it, whether its channels were errored, and which
channel joins it carried. Code continuing after user callbacks or
asynchronous waits checks whether the socket still uses its connection,
as callbacks can disconnect or connect at any time.
This fixes that:
- channels stayed joined when the close event arrived late or after
connect() replaced the connection, resulting in "unmatched topic"
- events of a replaced connection acted on its replacement
- heartbeats kept running after disconnecting, or were restarted by a
late reply, and then reconnected the socket
- teardowns, heartbeat timeouts and close or error handling reconnected
after callbacks had disconnected or connected
- disconnecting while connecting fell back to LongPoll, fallback
callbacks accumulated and acted on later attempts, and a normal close
by the server did not cancel the fallback health check
- connecting twice while disconnecting leaked a connection
- a stale buffered join was sent in addition to the rejoin
- a heartbeat timeout errored channels while still connected
- resume and visibilitychange tore down the connection they created
- callbacks removed while dispatching a message were skipped
- a normal close by the server was reconnected on visibility change,
while connect() did nothing
- channels joined by error callbacks were errored again
- asynchronous encoders and decoders acted on replaced or closed
connections
* Cancel pending LongPoll work when it stops
LongPoll aborted its requests when closing, but aborting can invoke the
request callbacks, synchronously for XHR and asynchronously for fetch,
and those still acted on the closed transport: a spurious error and a
retry, or making it active again after a disconnect. Messages queued
for delivery were still delivered after the close, a retry overrode a
close callback that closed it for good, and an open callback closing it
did not stop the next poll.
A request now belongs to the transport until it completes or the
transport stops, and callbacks of a request that does not belong to it
anymore are ignored. Stopping also cancels the queued messages and the
pending outgoing batch, a retry becomes active again before the close
callbacks run, and polling stops once the transport is closed.
* Pass the channel along with its pushes
Socket.push only received the message, so finding out whether a push
was outdated meant searching all channels for the one with its topic
and join ref, up to three times per push: when it was buffered and
flushed, when it was sent, and when its encoding finished.
Push now passes its channel to socket.push, which turns these checks
into comparing the push's join ref with the channel's current one and
checking whether the channel left.
* fmt
* Make socket tests catch the regressions they are meant for
Breaking the code each test guards showed that some tests no longer
failed for their regression:
- "does not throw when encoding finishes after disconnect completed"
could not fail anymore, as its join push is dropped as outdated before
it is sent. It now checks that a push is not sent once its connection
was replaced while it was encoded.
- "does not connect twice" only checked after the scheduled reconnect,
when an extra connection was still being torn down. It now also
checks right after connect().
- "ignores errors and messages of a connection that was replaced"
closed the old connection, which already detached its error handler.
The old connection now still drains its buffer.
- "does not error channels when the connection never opened" now
checks that the channel is not errored at all.
Also cover that replacing the transport while a teardown waits prevents
its reconnect, like disconnecting does.
* Cover dropping the pending LongPoll batch on retry
When LongPoll stops, it now also drops a batch that was still collecting
sends, as its timer is cancelled. Otherwise, sends after a retry were
appended to that batch and never sent.
Also remove the test for a timeout after close, as the tests for abort
callbacks after close and for timeouts after a retry cover it.
* Remove detaching the handlers of a torn down connection
A connection only handles the events of its transport while it is the
socket's current connection, so detaching the handlers of a replaced or
torn down connection made no difference.
* Cover cancelling a scheduled reconnect on a normal close
A normal close by the server cancels a scheduled reconnect, which would
otherwise tear down a connection created by calling connect() again
before the reconnect was due.
* Error channels after socket error callbacks replaced the connection
onConnError skipped erroring the channels when its callbacks replaced
the connection. However, when a callback replaced the transport of a
connection that already failed (and was therefore no longer open),
replaceTransport did not error the channels either, so they stayed
joined and did not rejoin on the next connection.
Always error the channels for the connection afterwards, which only
errors the joins it carried once it was replaced.
* Remove redundant checks of the fallback attempt
fallback() checked whether its attempt was still current, but the
fallback timer is cleared whenever the attempt ends, and the error and
open callbacks only call it while the attempt is current. The ping of
the health check checked it as well, but its reply can only arrive on
the connection that sent it, and it shows that the primary transport
works even after a disconnect.
* more fixes
* longpoll wasClean fix
* don't accumulate ping callbacks when connection drops1 parent 4c1df11 commit f35628b
7 files changed
Lines changed: 2089 additions & 136 deletions
File tree
- assets
- js/phoenix
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| 29 | + | |
29 | 30 | | |
| 31 | + | |
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
| |||
37 | 39 | | |
38 | 40 | | |
39 | 41 | | |
40 | | - | |
| 42 | + | |
| 43 | + | |
41 | 44 | | |
42 | 45 | | |
43 | 46 | | |
| |||
52 | 55 | | |
53 | 56 | | |
54 | 57 | | |
55 | | - | |
56 | | - | |
57 | | - | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
58 | 66 | | |
59 | 67 | | |
60 | 68 | | |
61 | | - | |
62 | | - | |
| 69 | + | |
63 | 70 | | |
64 | 71 | | |
65 | 72 | | |
66 | 73 | | |
67 | 74 | | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
68 | 78 | | |
69 | 79 | | |
70 | 80 | | |
| |||
75 | 85 | | |
76 | 86 | | |
77 | 87 | | |
78 | | - | |
79 | | - | |
| 88 | + | |
80 | 89 | | |
81 | 90 | | |
82 | 91 | | |
| |||
105 | 114 | | |
106 | 115 | | |
107 | 116 | | |
108 | | - | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
109 | 125 | | |
110 | 126 | | |
111 | 127 | | |
| |||
123 | 139 | | |
124 | 140 | | |
125 | 141 | | |
126 | | - | |
127 | | - | |
| 142 | + | |
128 | 143 | | |
129 | 144 | | |
130 | 145 | | |
| |||
156 | 171 | | |
157 | 172 | | |
158 | 173 | | |
159 | | - | |
160 | | - | |
161 | | - | |
| 174 | + | |
| 175 | + | |
162 | 176 | | |
163 | 177 | | |
164 | 178 | | |
| |||
171 | 185 | | |
172 | 186 | | |
173 | 187 | | |
174 | | - | |
175 | | - | |
176 | | - | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
177 | 204 | | |
178 | 205 | | |
179 | 206 | | |
180 | 207 | | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
181 | 217 | | |
182 | 218 | | |
183 | 219 | | |
| |||
186 | 222 | | |
187 | 223 | | |
188 | 224 | | |
189 | | - | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
190 | 230 | | |
191 | | - | |
192 | | - | |
| 231 | + | |
193 | 232 | | |
194 | 233 | | |
195 | 234 | | |
196 | 235 | | |
197 | | - | |
198 | | - | |
199 | | - | |
| 236 | + | |
| 237 | + | |
200 | 238 | | |
201 | | - | |
202 | 239 | | |
203 | 240 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
43 | | - | |
| 43 | + | |
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| |||
0 commit comments