fix(core): Fixes threading issue and adds threadguard (#565)

Setting `m.NetState = null` from a different thread caused `Mobile.Delta` and `NetState.Send` to run from a separate thread which cannot happen.
- [X] Fixes this issue
- [X] Adds THREADGUARD compiler constant as an option to trace this in the future.
This commit is contained in:
Kamron Batman 2021-04-08 21:56:31 -07:00 committed by GitHub
parent 47bbd72e55
commit 529abb4283
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 118 additions and 81 deletions

View file

@ -1447,93 +1447,96 @@ namespace Server
}
set
{
if (m_NetState != value)
if (m_NetState == value)
{
m_Map?.OnClientChange(m_NetState, value, this);
return;
}
m_Target?.Cancel(this, TargetCancelType.Disconnected);
#if THREADGUARD
if (Thread.CurrentThread != Core.Thread)
{
Utility.PushColor(ConsoleColor.Red);
Console.WriteLine("Attempting to set Mobile.NetState value from an invalid thread!");
Console.WriteLine(new StackTrace());
Utility.PopColor();
return;
}
#endif
m_Spell?.OnConnectionChanged();
m_Map?.OnClientChange(m_NetState, value, this);
m_Target?.Cancel(this, TargetCancelType.Disconnected);
m_Spell?.OnConnectionChanged();
m_NetState?.CancelAllTrades();
// if (m_Spell != null)
// m_Spell.FinishSequence();
var box = FindBankNoCreate();
m_NetState?.CancelAllTrades();
if (box?.Opened == true)
{
box.Close();
}
var box = FindBankNoCreate();
m_NetState = value;
if (box?.Opened == true)
if (m_NetState == null)
{
OnDisconnected();
EventSink.InvokeDisconnected(this);
// Disconnected, start the logout timer
if (m_LogoutTimer == null)
{
box.Close();
}
// REMOVED:
// m_Actions.Clear();
m_NetState = value;
if (m_NetState == null)
{
OnDisconnected();
EventSink.InvokeDisconnected(this);
// Disconnected, start the logout timer
if (m_LogoutTimer == null)
{
m_LogoutTimer = Timer.DelayCall(GetLogoutDelay(), Logout);
}
else
{
m_LogoutTimer.Stop();
m_LogoutTimer.Delay = GetLogoutDelay();
m_LogoutTimer.Start();
}
m_LogoutTimer = Timer.DelayCall(GetLogoutDelay(), Logout);
}
else
{
OnConnected();
EventSink.InvokeConnected(this);
// Connected, stop the logout timer and if needed, move to the world
m_LogoutTimer?.Stop();
m_LogoutTimer = null;
if (m_Map == Map.Internal && LogoutMap != null)
{
Map = LogoutMap;
Location = LogoutLocation;
}
m_LogoutTimer.Stop();
m_LogoutTimer.Delay = GetLogoutDelay();
m_LogoutTimer.Start();
}
for (var i = Items.Count - 1; i >= 0; --i)
{
if (i >= Items.Count)
{
continue;
}
var item = Items[i];
if (item is SecureTradeContainer)
{
for (var j = item.Items.Count - 1; j >= 0; --j)
{
if (j < item.Items.Count)
{
item.Items[j].OnSecureTrade(this, this, this, false);
AddToBackpack(item.Items[j]);
}
}
Timer.DelayCall(item.Delete);
}
}
DropHolding();
OnNetStateChanged();
}
else
{
OnConnected();
EventSink.InvokeConnected(this);
// Connected, stop the logout timer and if needed, move to the world
m_LogoutTimer?.Stop();
m_LogoutTimer = null;
if (m_Map == Map.Internal && LogoutMap != null)
{
Map = LogoutMap;
Location = LogoutLocation;
}
}
for (var i = Items.Count - 1; i >= 0; --i)
{
if (i >= Items.Count)
{
continue;
}
var item = Items[i];
if (item is SecureTradeContainer)
{
for (var j = item.Items.Count - 1; j >= 0; --j)
{
if (j < item.Items.Count)
{
item.Items[j].OnSecureTrade(this, this, this, false);
AddToBackpack(item.Items[j]);
}
}
Timer.DelayCall(item.Delete);
}
}
DropHolding();
OnNetStateChanged();
}
}
@ -7922,6 +7925,17 @@ namespace Server
public virtual void Delta(MobileDelta flag)
{
#if THREADGUARD
if (Thread.CurrentThread != Core.Thread)
{
Utility.PushColor(ConsoleColor.Red);
Console.WriteLine("Attempting to queue a delta change from an invalid thread!");
Console.WriteLine(new StackTrace());
Utility.PopColor();
return;
}
#endif
if (m_Map == null || m_Map == Map.Internal || Deleted)
{
return;

View file

@ -17,6 +17,7 @@ using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
@ -453,6 +454,16 @@ namespace Server.Network
public bool GetSendBuffer(out CircularBuffer<byte> cBuffer)
{
#if THREADGUARD
if (Thread.CurrentThread != Core.Thread)
{
Utility.PushColor(ConsoleColor.Red);
Console.WriteLine("Attempting to get pipe buffer from wrong thread!");
Console.WriteLine(new StackTrace());
Utility.PopColor();
return;
}
#endif
var result = SendPipe.Writer.TryGetMemory();
cBuffer = new CircularBuffer<byte>(result.Buffer);
@ -1056,13 +1067,6 @@ namespace Server.Network
return;
}
var m = Mobile;
if (m != null)
{
m.NetState = null;
Mobile = null;
}
try
{
if (_disconnectReason != string.Empty)
@ -1108,9 +1112,28 @@ namespace Server.Network
{
TraceDisconnect(_disconnectReason, _toString);
#if THREADGUARD
if (Thread.CurrentThread != Core.Thread)
{
Utility.PushColor(ConsoleColor.Red);
Console.WriteLine("Attempting to dispose a netstate from an invalid thread!");
Console.WriteLine(new StackTrace());
Utility.PopColor();
return;
}
#endif
RecvPipe.Writer.Close();
SendPipe.Writer.Close();
var m = Mobile;
Mobile = null;
if (m?.NetState == this)
{
m.NetState = null;
}
var a = Account;
Gumps.Clear();