@@ -45,8 +45,9 @@ class Session{
45
45
46
46
public const STATE_CONNECTING = 0 ;
47
47
public const STATE_CONNECTED = 1 ;
48
- public const STATE_DISCONNECTING = 2 ;
49
- public const STATE_DISCONNECTED = 3 ;
48
+ public const STATE_DISCONNECT_PENDING = 2 ;
49
+ public const STATE_DISCONNECT_NOTIFIED = 3 ;
50
+ public const STATE_DISCONNECTED = 4 ;
50
51
51
52
public const MIN_MTU_SIZE = 400 ;
52
53
@@ -149,7 +150,10 @@ public function isTemporal() : bool{
149
150
}
150
151
151
152
public function isConnected () : bool {
152
- return $ this ->state !== self ::STATE_DISCONNECTING and $ this ->state !== self ::STATE_DISCONNECTED ;
153
+ return
154
+ $ this ->state !== self ::STATE_DISCONNECT_PENDING and
155
+ $ this ->state !== self ::STATE_DISCONNECT_NOTIFIED and
156
+ $ this ->state !== self ::STATE_DISCONNECTED ;
153
157
}
154
158
155
159
public function update (float $ time ) : void {
@@ -159,12 +163,18 @@ public function update(float $time) : void{
159
163
return ;
160
164
}
161
165
162
- if ($ this ->state === self ::STATE_DISCONNECTING ){
166
+ if ($ this ->state === self ::STATE_DISCONNECT_PENDING || $ this -> state === self :: STATE_DISCONNECT_NOTIFIED ){
163
167
//by this point we already told the event listener that the session is closing, so we don't need to do it again
164
168
if (!$ this ->sendLayer ->needsUpdate () and !$ this ->recvLayer ->needsUpdate ()){
165
- $ this ->state = self ::STATE_DISCONNECTED ;
166
- $ this ->logger ->debug ("Client cleanly disconnected, marking session for destruction " );
167
- return ;
169
+ if ($ this ->state === self ::STATE_DISCONNECT_PENDING ){
170
+ $ this ->queueConnectedPacket (new DisconnectionNotification (), PacketReliability::RELIABLE_ORDERED , 0 , true );
171
+ $ this ->state = self ::STATE_DISCONNECT_NOTIFIED ;
172
+ $ this ->logger ->debug ("All pending traffic flushed, sent disconnect notification " );
173
+ }else {
174
+ $ this ->state = self ::STATE_DISCONNECTED ;
175
+ $ this ->logger ->debug ("Client cleanly disconnected, marking session for destruction " );
176
+ return ;
177
+ }
168
178
}elseif ($ this ->disconnectionTime + 10 < $ time ){
169
179
$ this ->state = self ::STATE_DISCONNECTED ;
170
180
$ this ->logger ->debug ("Timeout during graceful disconnect, forcibly closing session " );
@@ -238,7 +248,7 @@ private function handleEncapsulatedPacketRoute(EncapsulatedPacket $packet) : voi
238
248
}
239
249
}
240
250
}elseif ($ id === DisconnectionNotification::$ ID ){
241
- $ this ->initiateDisconnect ( " client disconnect " );
251
+ $ this ->onClientDisconnect ( );
242
252
}elseif ($ id === ConnectedPing::$ ID ){
243
253
$ dataPacket = new ConnectedPing ();
244
254
$ dataPacket ->decode (new PacketSerializer ($ packet ->buffer ));
@@ -290,9 +300,8 @@ public function handlePacket(Packet $packet) : void{
290
300
*/
291
301
public function initiateDisconnect (string $ reason ) : void {
292
302
if ($ this ->isConnected ()){
293
- $ this ->state = self ::STATE_DISCONNECTING ;
303
+ $ this ->state = self ::STATE_DISCONNECT_PENDING ;
294
304
$ this ->disconnectionTime = microtime (true );
295
- $ this ->queueConnectedPacket (new DisconnectionNotification (), PacketReliability::RELIABLE_ORDERED , 0 , true );
296
305
$ this ->server ->getEventListener ()->onClientDisconnect ($ this ->internalId , $ reason );
297
306
$ this ->logger ->debug ("Requesting graceful disconnect because \"$ reason \"" );
298
307
}
@@ -307,6 +316,20 @@ public function forciblyDisconnect(string $reason) : void{
307
316
$ this ->logger ->debug ("Forcibly disconnecting session due to \"$ reason \"" );
308
317
}
309
318
319
+ private function onClientDisconnect () : void {
320
+ //the client will expect an ACK for this; make sure it gets sent, because after forcible termination
321
+ //there won't be any session ticks to update it
322
+ $ this ->recvLayer ->update ();
323
+
324
+ if ($ this ->isConnected ()){
325
+ //the client might have disconnected after the server sent a disconnect notification, but before the client
326
+ //received it - in this case, we don't want to notify the event handler twice
327
+ $ this ->server ->getEventListener ()->onClientDisconnect ($ this ->internalId , "client disconnect " );
328
+ }
329
+ $ this ->state = self ::STATE_DISCONNECTED ;
330
+ $ this ->logger ->debug ("Terminating session due to client disconnect " );
331
+ }
332
+
310
333
/**
311
334
* Returns whether the session is ready to be destroyed (either properly cleaned up or forcibly terminated)
312
335
*/
0 commit comments