Skip to content

Drain send queue on disconnect and ensure session cleanup#630

Merged
AngeloTadeucci merged 2 commits intomasterfrom
dev
Feb 9, 2026
Merged

Drain send queue on disconnect and ensure session cleanup#630
AngeloTadeucci merged 2 commits intomasterfrom
dev

Conversation

@AngeloTadeucci
Copy link
Collaborator

@AngeloTadeucci AngeloTadeucci commented Feb 9, 2026

Summary by CodeRabbit

  • Bug Fixes
    • Ensured sessions are reliably disconnected after migrations and related flows.
    • Improved delivery of queued outbound packets during disconnect to avoid lost data.
    • Simplified session shutdown checks to reduce race conditions and improve stability.
  • Tests
    • Made timing in event-queue tests more robust for CI by replacing coarse sleeps with a retry-driven wait.

Make send worker a field and drain the send queue during disconnect by calling sendQueue.CompleteAdding() and joining the worker thread, allowing queued packets (e.g. migration) to be delivered before close. Remove disconnecting checks in SendRaw/SendWorker so the worker can finish draining even when a disconnect is in progress. Move/ensure session.Disconnect() calls into finally blocks across several handlers (DungeonManager, ChannelHandler, QuitHandler, GameSession, CharacterManagementHandler) to guarantee cleanup. Also fix the send worker thread variable usage when starting the thread.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

Refactors Session send-worker lifecycle and tightens session cleanup: introduces a dedicated send worker thread, drains and joins the send queue on disconnect, relaxes early SendRaw checks, and adds finally-driven guaranteed session.Disconnect() calls across migration and login handlers.

Changes

Cohort / File(s) Summary
Session Infrastructure
Maple2.Server.Core/Network/Session.cs
Introduce a dedicated sendWorkerThread field; start it at init; on Disconnect drain sendQueue and join the thread with timeout; remove disconnecting-based early exits so loops/checks use only disposed.
Game Migration & Handlers
Maple2.Server.Game/Manager/DungeonManager.cs, Maple2.Server.Game/PacketHandlers/ChannelHandler.cs, Maple2.Server.Game/PacketHandlers/QuitHandler.cs, Maple2.Server.Game/Session/GameSession.cs
Add finally blocks to migration-related flows to ensure session.Disconnect() is always called after migration attempts (success or exception).
Login Character Selection
Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs
Call session.Disconnect() immediately after sending the LoginToGame migration packet, closing the login session once migration is initiated.
Tests / Timing
Maple2.Server.Tests/Tools/EventQueueTests.cs
Replace coarse sleep-based wait with a retry loop invoking the queue until expected executions occur (improves CI timing robustness).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client as Client
    participant Session as Session
    participant SendWorker as SendWorker\n(thread)
    participant Socket as NetworkSocket
    rect rgba(200,220,255,0.5)
    Client->>Session: Enqueue packet / SendRaw()
    Session->>SendWorker: Signal sendQueue
    SendWorker->>Socket: Write packets
    Socket-->>SendWorker: ACK/complete
    end
    rect rgba(200,255,200,0.5)
    Client->>Session: Request Disconnect()
    Session->>SendWorker: Stop request (dispose)
    Session->>SendWorker: Drain sendQueue
    SendWorker->>Socket: Flush remaining packets
    SendWorker-->>Session: Thread join (timeout)
    Session-->>Client: Disconnected
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • Zintixx
  • jf52637

Poem

🐰 I hopped through queues and threading night,
I nudged the worker to finish right,
Drained every packet, joined the run,
Now sessions sleep when work is done. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning One change in EventQueueTests.cs appears unrelated to the core session cleanup objective; it modifies test timing logic rather than addressing session management. Clarify whether EventQueueTests.cs timing adjustments are necessary for the session cleanup fix or if they should be submitted in a separate pull request.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: draining the send queue on disconnect and ensuring proper session cleanup across multiple handlers.
Linked Issues check ✅ Passed The changes directly address issue #628 by ensuring session cleanup on disconnect and draining pending packets, preventing lingering sessions from interfering with subsequent login attempts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

No actionable comments were generated in the recent review. 🎉


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Maple2.Server.Game/PacketHandlers/ChannelHandler.cs (1)

41-51: ⚠️ Potential issue | 🟠 Major

Error path now disconnects the player, which may be a UX regression.

With the finally block, if MigrateOut fails (RPC exception), the player is disconnected from the game entirely. Previously, the error path likely kept the session alive so the player could remain on their current channel after seeing the error notice.

Consider whether the error path should preserve the session. If so, the finally block approach doesn't work here — you'd need the explicit disconnect only on success:

Proposed fix: disconnect only on success
         try {
             // ... migration request ...
             session.Send(MigrationPacket.GameToGame(endpoint, response.Token, session.Field?.MapId ?? 0));
             session.State = SessionState.ChangeChannel;
+            session.Disconnect();
         } catch (RpcException ex) {
             Logger.Error(ex, "Failed to migrate to channel {Channel}", channel);
 
             session.Send(NoticePacket.MessageBox(new InterfaceText("Channel is unavailable, close the channel list and try again.")));
 
             // Update the client with the latest channel list.
             ChannelsResponse response = World.Channels(new ChannelsRequest());
             session.Send(ChannelPacket.Load(response.Channels));
-        } finally {
-            session.Disconnect();
         }
🧹 Nitpick comments (1)
Maple2.Server.Core/Network/Session.cs (1)

306-329: SendInternal still guards with disconnecting == 1, preventing new packets from being enqueued after disconnect begins.

This is correct — only the already-queued packets should be drained, not new ones. The packet.Take(length).ToArray() copy is needed since the buffer may be pooled/reused.

One minor observation: lastSentPackets[op] = packet.Take(length).ToArray() at Line 317 and byte[] packetCopy = packet.Take(length).ToArray() at Line 322 allocate two separate copies. You could reuse packetCopy for both.

♻️ Avoid double allocation
     private void SendInternal(byte[] packet, int length) {
         if (disposed || disconnecting == 1) return;
 `#if` DEBUG
         LogSend(packet, length);
 `#endif`
+        byte[] packetCopy = packet.Take(length).ToArray();
         // Track last sent packet by opcode
         if (length >= 2) {
             var op = (SendOp) (packet[1] << 8 | packet[0]);
-            // Store a copy to avoid mutation issues
-            lastSentPackets[op] = packet.Take(length).ToArray();
+            lastSentPackets[op] = packetCopy;
         }
 
         // Queue the raw packet for background processing
-        // Make a copy since the caller may reuse the buffer
-        byte[] packetCopy = packet.Take(length).ToArray();
         try {
             sendQueue.Add((packetCopy, length));
         } catch (InvalidOperationException) {

Replace a single Thread.Sleep(25) with a retry loop that sleeps 10ms and calls queue.InvokeAll up to 10 times until the scheduled action runs. This reduces flakiness by handling timing imprecision in CI environments.
@AngeloTadeucci AngeloTadeucci merged commit 426dc84 into master Feb 9, 2026
4 checks passed
@AngeloTadeucci AngeloTadeucci deleted the dev branch February 9, 2026 01:56
@mfranca0009
Copy link

@AngeloTadeucci, the issue from #628 still occurs even after this change.

@AngeloTadeucci
Copy link
Collaborator Author

@AngeloTadeucci, the issue from #628 still occurs even after this change.

Weird I tried replicating and wasn't able to! I used 2 clients logging in into the same account and a bunch of other stuff. Did you pull the changes right?

@mfranca0009
Copy link

@AngeloTadeucci, the issue from #628 still occurs even after this change.

Weird I tried replicating and wasn't able to! I used 2 clients logging in into the same account and a bunch of other stuff. Did you pull the changes right?

My environment was borked, fresh repo fixed it. Apologies!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Any login attempt after the first with the same credentials fails with error

3 participants