Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions Maple2.Server.Core/Network/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,18 +206,45 @@ private void PerformHandshake() {

private async Task WriteRecvPipe(Socket socket, PipeWriter writer) {
try {
FlushResult result;
FlushResult result = default;
do {
if (disposed) break;

Memory<byte> memory = writer.GetMemory();
int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None);
if (bytesRead <= 0) {
int bytesRead;

try {
bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None);
} catch (SocketException sockEx) when (sockEx.ErrorCode == 995 || sockEx.ErrorCode == 10004) {
// 995: Operation aborted (thread exit/app request)
// 10004: Interrupted system call
// These are expected when closing the session
Logger.Debug("Socket closed during receive (code {ErrorCode}) account={AccountId} char={CharacterId}",
sockEx.ErrorCode, AccountId, CharacterId);
break;
}

writer.Advance(bytesRead);
if (bytesRead <= 0 || disposed) {
break;
}

result = await writer.FlushAsync();
// Check if writer was completed before advancing
try {
writer.Advance(bytesRead);
result = await writer.FlushAsync();
} catch (InvalidOperationException) when (disposed) {
// Writer was completed/disposed during advance or flush
Logger.Debug("Pipe writer completed during operation account={AccountId} char={CharacterId}", AccountId, CharacterId);
break;
} catch (ArgumentOutOfRangeException) when (disposed) {
// Invalid byte count during disposal
Logger.Debug("Pipe writer advance failed during disposal account={AccountId} char={CharacterId}", AccountId, CharacterId);
break;
}
} while (!disposed && !result.IsCompleted);
} catch (Exception ex) when (disposed) {
// Suppress exceptions if we're already disposed
Logger.Debug(ex, "WriteRecvPipe exception during disposal account={AccountId} char={CharacterId}", AccountId, CharacterId);
} catch (Exception ex) {
Logger.Debug(ex, "WriteRecvPipe exception account={AccountId} char={CharacterId}", AccountId, CharacterId);
Disconnect();
Expand Down Expand Up @@ -319,6 +346,10 @@ private void SendRaw(ByteWriter packet) {
if (writeTask.IsFaulted) {
throw writeTask.Exception?.GetBaseException() ?? new Exception("Write task faulted");
}
} catch (Exception ex) when (ex.InnerException is IOException or SocketException || ex is IOException or SocketException) {
// Expected when client closes the connection (e.g., during migration)
Logger.Debug("SendRaw connection closed account={AccountId} char={CharacterId}", AccountId, CharacterId);
Disconnect();
} catch (Exception ex) {
Logger.Warning(ex, "[LIFECYCLE] SendRaw write failed account={AccountId} char={CharacterId}", AccountId, CharacterId);
Disconnect();
Expand Down
10 changes: 8 additions & 2 deletions Maple2.Server.Game/PacketHandlers/QuitHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,15 @@ public override void Handle(GameSession session, IByteReader packet) {
MigrateOutResponse response = World.MigrateOut(request);
var endpoint = new IPEndPoint(IPAddress.Parse(response.IpAddress), response.Port);
session.Send(MigrationPacket.GameToLogin(endpoint, response.Token));
} catch (RpcException) {
// Do NOT disconnect here — let the client close the TCP connection after
// receiving the migration packet. Calling Disconnect() immediately would
// set disconnecting=1, causing SendWorker to drop the queued packet.
// The natural TCP close will trigger the full Dispose chain (leave field,
// update PlayerInfo, save state, etc.).
} catch (RpcException ex) {
Logger.Error(ex, "MigrateOut failed for account={AccountId} char={CharacterId}",
session.AccountId, session.CharacterId);
session.Send(MigrationPacket.GameToLoginError(s_move_err_default));
} finally {
session.Disconnect();
}
}
Expand Down
14 changes: 7 additions & 7 deletions Maple2.Server.Game/Session/GameSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ protected override void Dispose(bool disposing) {
LastOnlineTime = DateTime.UtcNow.ToEpochSeconds(),
MapId = 0,
Channel = -1,
Async = true,
Async = false,
});

Party.CheckDisband();
Expand Down Expand Up @@ -795,10 +795,6 @@ protected override void Dispose(bool disposing) {
}
return;

void TrySaveComponent(GameStorage.Request db, Action<GameStorage.Request> action) {
try { action(db); } catch (Exception ex) { Logger.Error(ex, "Error saving component for {Player}", PlayerName); }
}

void SafeDispose(IDisposable? disp) {
if (disp == null) return;
try { disp.Dispose(); } catch (Exception ex) { Logger.Error(ex, "Error disposing component for {Player}", PlayerName); }
Expand Down Expand Up @@ -853,6 +849,11 @@ void SaveCacheConfig() {

public void MigrationSave() {
if (preMigrationSaved) return;
preMigrationSaved = true;
SavePlayerState();
}

private void SavePlayerState() {
try {
AcquireLock(AccountId, 5);
using GameStorage.Request db = GameStorage.Context();
Expand All @@ -870,9 +871,8 @@ public void MigrationSave() {
TrySaveComponent(db, Dungeon.Save);
db.Commit();
db.SaveChanges();
preMigrationSaved = true;
} catch (Exception ex) {
Logger.Error(ex, "MigrationSave failed AccountId={AccountId} CharacterId={CharacterId}", AccountId, CharacterId);
Logger.Error(ex, "SavePlayerState failed AccountId={AccountId} CharacterId={CharacterId}", AccountId, CharacterId);
} finally {
ReleaseLock(AccountId);
}
Expand Down
12 changes: 9 additions & 3 deletions Maple2.Server.Login/Session/LoginSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,16 @@ protected override void Dispose(bool disposing) {

try {
Server.OnDisconnected(this);
State = SessionState.Disconnected;
Complete();
} finally {
} catch (Exception ex) {
Logger.Debug(ex, "Error during LoginSession.OnDisconnected");
}

State = SessionState.Disconnected;

try {
base.Dispose(disposing);
} catch (Exception ex) {
Logger.Debug(ex, "Error during LoginSession base disposal");
}
Comment thread
AngeloTadeucci marked this conversation as resolved.
}
#endregion
Expand Down
Loading