Skip to content

Harden Session I/O and migration save flow - #627

Merged
AngeloTadeucci merged 3 commits into
masterfrom
dev
Feb 7, 2026
Merged

Harden Session I/O and migration save flow#627
AngeloTadeucci merged 3 commits into
masterfrom
dev

Conversation

@AngeloTadeucci

@AngeloTadeucci AngeloTadeucci commented Feb 7, 2026

Copy link
Copy Markdown
Collaborator

Improve robustness around network IO, disposal, migration and save behavior across sessions:

  • Core.Network.Session.cs: Add defensive checks and richer exception handling in the receive pipe loop (handle expected socket errors 995/10004, guard against disposed writer/advance races, suppress exceptions during disposal) and catch IO/Socket exceptions in SendRaw to log and disconnect cleanly.

  • Game/PacketHandlers/QuitHandler.cs: Add logging for MigrateOut RPC failures and annotate migration behavior to avoid prematurely dropping the migration packet (adjusted exception handling around migration send).

  • Game/Session/GameSession.cs: Change leave save to Async=false, refactor migration save flow by extracting SavePlayerState(), set preMigrationSaved earlier, and adjust error logging. Removed a local TrySaveComponent helper.

  • Login/Session/LoginSession.cs: Reorder disposal to call base.Dispose first to stop network operations, add Serilog debug logging for disposal errors.

These changes are intended to make session teardown and migration more reliable and to avoid races/ignored errors when clients or sockets close during operations.

Summary by CodeRabbit

  • Bug Fixes

    • Improved network stability with graceful handling of socket/IO errors and disposed connections to prevent crashes during shutdown or migration.
    • Fixed edge-case errors when writing network data that could interrupt sessions.
  • Refactor

    • Consolidated and made player-state persistence more reliable during migrations and session teardown.
  • Logging

    • Added targeted debug/error logs to aid diagnosis of connection and save failures.

Improve robustness around network IO, disposal, migration and save behavior across sessions:

- Core.Network.Session.cs: Add defensive checks and richer exception handling in the receive pipe loop (handle expected socket errors 995/10004, guard against disposed writer/advance races, suppress exceptions during disposal) and catch IO/Socket exceptions in SendRaw to log and disconnect cleanly.

- Game/PacketHandlers/QuitHandler.cs: Add logging for MigrateOut RPC failures and annotate migration behavior to avoid prematurely dropping the migration packet (adjusted exception handling around migration send).

- Game/Session/GameSession.cs: Change leave save to Async=false, refactor migration save flow by extracting SavePlayerState(), set preMigrationSaved earlier, and adjust error logging. Removed a local TrySaveComponent helper.

- Login/Session/LoginSession.cs: Reorder disposal to call base.Dispose first to stop network operations, add Serilog debug logging for disposal errors.

These changes are intended to make session teardown and migration more reliable and to avoid races/ignored errors when clients or sockets close during operations.
@coderabbitai

coderabbitai Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@AngeloTadeucci has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 14 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Add defensive exception handling to core session receive/send paths, harden disposal flows in Login/Game sessions, and refactor GameSession migration save into a new SavePlayerState method for batched persistence.

Changes

Cohort / File(s) Summary
Network session I/O
Maple2.Server.Core/Network/Session.cs
Hardened WriteRecvPipe: initialize result, early-exit on disposed, catch SocketException (handle codes 995/10004), guard writer.Advance/writer.FlushAsync against disposal (InvalidOperationException, ArgumentOutOfRangeException), and log/contextually disconnect on other exceptions. Extended SendRaw to catch/handle IOException/SocketException (including inner exceptions) and disconnect with debug logging.
Game session persistence refactor
Maple2.Server.Game/Session/GameSession.cs
Moved inline migration-save logic into new SavePlayerState method that batches component saves (UgcMarket, Config, Shop, Item, Survival, Housing, GameEvent, Achievement, Quest, Dungeon). Dispose now sends PlayerUpdateRequest with Async = false; MigrationSave sets preMigrationSaved and delegates to SavePlayerState.
Session disposal & RPC error logging
Maple2.Server.Game/PacketHandlers/QuitHandler.cs, Maple2.Server.Login/Session/LoginSession.cs
QuitHandler: replace empty RpcException catch with logging and clarify comment about natural TCP disconnect after migration. LoginSession.Dispose: removed Complete() from finally, moved State = Disconnected outside, split disposal into guarded try/catch blocks and added disposal-stage exception logging and guarded base.Dispose(disposing).

Sequence Diagram(s)

sequenceDiagram
participant Client
participant Socket
participant Session
participant PipeWriter as "Pipe Writer"
participant Consumer as "Recv Consumer"

Client->>Socket: send bytes
Socket->>Session: ReceiveAsync() returns buffer
Session->>PipeWriter: writer.Write(buffer)
PipeWriter->>Session: Advance()
Session->>PipeWriter: FlushAsync()
PipeWriter->>Consumer: data available
alt SocketException (995/10004) or disposed
Session->>Session: break loop / exit gracefully
end
Loading
sequenceDiagram
participant GameSession
participant SavePlayerState
participant Repo as "Persistence/Repo"
GameSession->>SavePlayerState: invoke during MigrationSave/Dispose
SavePlayerState->>Repo: batch-save components (UgcMarket,...,Dungeon)
Repo-->>SavePlayerState: success / error
alt error
SavePlayerState->>GameSession: log "SavePlayerState failed ..."
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Zintixx
  • jf52637

Poem

🐰 I hopped through sockets, nibbling stray exceptions,
Brought order to saves with tidy collections,
When disposal tipped the table and bytes took flight,
I stitched the seams and set the state aright.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'Harden Session I/O and migration save flow' directly and accurately reflects the main objectives: hardening session I/O handling (defensive checks, exception handling in receive/send loops) and improving the migration save flow (refactored SavePlayerState, reordered disposal). The title is concise, specific, and clearly summarizes the primary focus.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

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

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@Maple2.Server.Login/Session/LoginSession.cs`:
- Around line 167-175: The Dispose path currently calls
Server.OnDisconnected(this) and base.Dispose(disposing) inside one try, so if
Server.OnDisconnected throws the base.Dispose call is skipped; change
Dispose(bool disposing) to call Server.OnDisconnected(this) inside its own
try/catch (or catch and swallow/log) and ensure base.Dispose(disposing) runs in
a finally block (or its own try) so network shutdown always executes; also
replace Log.Logger.Debug(...) with the instance Logger.Debug(...) when logging
exceptions from Server.OnDisconnected to keep the inherited ForContext
enrichment.
🧹 Nitpick comments (3)
Maple2.Server.Game/Session/GameSession.cs (2)

850-854: Setting preMigrationSaved = true before SavePlayerState() — intentional but worth a note.

If SavePlayerState() throws, the flag prevents any retry during Dispose. This is likely intentional to avoid double-save races, but it means a transient DB failure during migration will result in unsaved player state with no recovery path. Consider whether a failed save should reset the flag or at least log at a higher severity.


856-883: SavePlayerState — partial commits possible when individual component saves fail.

TrySaveComponent swallows per-component exceptions, so db.Commit() at line 872 will commit whatever succeeded. This means a player could end up with, e.g., saved items but lost quest progress. If atomicity is desired, the component exceptions should propagate to skip the commit. If partial saves are acceptable (existing behavior), this is fine as-is.

Maple2.Server.Core/Network/Session.cs (1)

216-225: Use SocketErrorCode with SocketError enum for cross-platform socket error handling.

The raw error codes 995 and 10004 are Windows-specific (WinSock). On Linux/macOS, SocketException.ErrorCode returns platform-native values, making this comparison unreliable. .NET normalizes these codes via SocketErrorCode property, which maps to portable SocketError enum members:

-} catch (SocketException sockEx) when (sockEx.ErrorCode == 995 || sockEx.ErrorCode == 10004) {
+} catch (SocketException sockEx) when (
+    sockEx.SocketErrorCode == SocketError.OperationAborted ||
+    sockEx.SocketErrorCode == SocketError.Interrupted) {

Comment thread Maple2.Server.Login/Session/LoginSession.cs
@AngeloTadeucci
AngeloTadeucci merged commit 8d8efd8 into master Feb 7, 2026
2 of 3 checks passed
@AngeloTadeucci
AngeloTadeucci deleted the dev branch February 7, 2026 19:01
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.

2 participants