aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfusion32 <marcopuzziello@gmail.com>2025-07-19 11:35:11 -0300
committerfusion32 <marcopuzziello@gmail.com>2025-07-19 11:35:11 -0300
commitd359c0af4874534efa810729d47f610a29687e93 (patch)
tree9a89b06444819e7e5cef84aa826682b3b01bfd03
parent638244fd078fe91971e57b1d06e4499268d99c42 (diff)
downloadgame-d359c0af4874534efa810729d47f610a29687e93.tar.gz
game-d359c0af4874534efa810729d47f610a29687e93.zip
THREADING: upgrade from LinuxThreads to NPTL
This was a required change to make the server run on a modern GLIBC version without resorting to shady shared libraries or compatibility tricks. It is very straightforward but we should always keep an eye out for possible bugs. There was also a problem with the Z loop on field sending functions that was causing the server to hang in an infinite loop and should now be fixed. With these changes it is now possible to login into the game for a split second before the client asserting.
-rw-r--r--TODO.md5
-rw-r--r--src/common.hh13
-rw-r--r--src/communication.cc58
-rw-r--r--src/connections.cc62
-rw-r--r--src/connections.hh7
-rw-r--r--src/crplayer.cc16
-rw-r--r--src/main.cc172
-rw-r--r--src/moveuse.cc2
-rw-r--r--src/reader.cc4
-rw-r--r--src/receiving.cc7
-rw-r--r--src/sending.cc11
-rw-r--r--src/shm.cc18
12 files changed, 248 insertions, 127 deletions
diff --git a/TODO.md b/TODO.md
index 165acd0..d1352f7 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,11 +1,6 @@
## Synchronization
I'm not sure whether synchronization is done properly. The implementation of the first few `crplayer.cc` functions left me with a taste of race conditions although integer loads on x86 are generally atomic.
-## Threading
-After some initial dive into `communication.cc`, it has become clear that the original codebase relied on `LinuxThreads` which, among its quirks, assigns different PIDs to different threads, explaining the usage of `getpid()`. It also seems it could lack automatically managed thread stacks which explains `CommunicationThreadStacks`.
-
-We'll have to address this BEFORE trying to run the server.
-
## TODO (LEFTOVER)
- Include `x == 0xFFFF` inside `CheckVisibility`?
- The map container uses type id zero but it seems to also be used as a "no type"
diff --git a/src/common.hh b/src/common.hh
index b97de3c..971dca2 100644
--- a/src/common.hh
+++ b/src/common.hh
@@ -78,12 +78,16 @@ typedef size_t usize;
// Nevertheless, we should focus on getting it working as intended, on the target
// platform (which is Linux 32-bit) before attempting to refine it.
STATIC_ASSERT(OS_LINUX);
-STATIC_ASSERT(sizeof(bool) == 1);
-STATIC_ASSERT(sizeof(int) == 4);
-//STATIC_ASSERT(sizeof(void*) == 4);
#include <errno.h>
#include <unistd.h>
+// NOTE(fusion): This is the member name for the thread id in `struct sigevent`
+// when `sigev_notify` is `SIGEV_THREAD_ID` but for whatever reason glibc doesn't
+// define it.
+#ifndef sigev_notify_thread_id
+# define sigev_notify_thread_id _sigev_un._tid
+#endif
+
// shm.cc
// =============================================================================
void StartGame(void);
@@ -93,7 +97,8 @@ bool LoginAllowed(void);
bool GameRunning(void);
bool GameStarting(void);
bool GameEnding(void);
-pid_t GetGameThreadPID(void);
+pid_t GetGameProcessID(void);
+pid_t GetGameThreadID(void);
int GetPrintlogPosition(void);
char *GetPrintlogLine(int Line);
void IncrementObjectCounter(void);
diff --git a/src/communication.cc b/src/communication.cc
index ffaa3a8..cdfb6c7 100644
--- a/src/communication.cc
+++ b/src/communication.cc
@@ -26,7 +26,7 @@
static const int TERMINALVERSION[] = {770, 770, 770};
static int TCPSocket;
static ThreadHandle AcceptorThread;
-static pid_t AcceptorThreadPID;
+static pid_t AcceptorThreadID;
static int ActiveConnections;
static Semaphore RSAMutex(1);
@@ -65,7 +65,7 @@ void GetCommunicationThreadStack(int *StackNumber, void **Stack){
for(int i = 0; i < NumberOfFreeCommunicationThreadStacks; i += 1){
int FreeStack = FreeCommunicationThreadStacks[i];
if(LastUsingCommunicationThread[FreeStack] == 0
- || kill(LastUsingCommunicationThread[FreeStack], 0) == -1){
+ || tgkill(GetGameProcessID(), LastUsingCommunicationThread[FreeStack], 0) == -1){
// NOTE(fusion): A little swap and pop action.
NumberOfFreeCommunicationThreadStacks -= 1;
FreeCommunicationThreadStacks[i] = FreeCommunicationThreadStacks[NumberOfFreeCommunicationThreadStacks];
@@ -79,7 +79,7 @@ void GetCommunicationThreadStack(int *StackNumber, void **Stack){
}
void AttachCommunicationThreadStack(int StackNumber){
- LastUsingCommunicationThread[StackNumber] = getpid();
+ LastUsingCommunicationThread[StackNumber] = gettid();
}
void ReleaseCommunicationThreadStack(int StackNumber){
@@ -663,8 +663,9 @@ bool DrainSocket(TConnection *Connection, int Size){
bool CallGameThread(TConnection *Connection){
if(GameRunning()){
Connection->WaitingForACK = true;
- if(kill(GetGameThreadPID(), SIGUSR1) != 0){
- error("CallGameThread: Can't send SIGUSR1 to pid %d\n", GetGameThreadPID());
+ if(tgkill(GetGameProcessID(), GetGameThreadID(), SIGUSR1) == -1){
+ error("CallGameThread: Can't send SIGUSR1 to thread %d/%d: (%d) %s\n",
+ GetGameProcessID(), GetGameThreadID(), errno, strerrordesc_np(errno));
SendLoginMessage(Connection, 20,
"The server is not online.\nPlease try again later.", -1);
return false;
@@ -880,7 +881,7 @@ TPlayerData *PerformRegistration(TConnection *Connection, char *PlayerName,
return NULL;
}
- bool Locked = PlayerData->Locked == getpid();
+ bool Locked = (PlayerData->Locked == gettid());
// TODO(fusion): How come this isn't a race condition? Perhaps these are
// constant and can only change when loaded from the database?
@@ -1037,7 +1038,7 @@ bool HandleLogin(TConnection *Connection){
return false;
}
- bool SlotLocked = (Slot->Locked == getpid());
+ bool SlotLocked = (Slot->Locked == gettid());
// NOTE(fusion): These checks would have been already made if the player was
// in the waiting list so they'd be redundant.
@@ -1183,7 +1184,7 @@ bool ReceiveCommand(TConnection *Connection){
}
if(Connection->State == CONNECTION_CONNECTED){
- alarm(0);
+ Connection->StopLoginTimer();
Connection->InDataSize = Size;
if(!HandleLogin(Connection)){
return false;
@@ -1219,8 +1220,8 @@ bool ReceiveCommand(TConnection *Connection){
}
// NOTE(fusion): We set `SigIOPending` here to signal that there could be more
- // packets already queued up, so `CommunicationThread` can attempt to read them
- // without waiting for another `SIGIO`.
+ // packets already queued up, in which case `CommunicationThread` will attempt
+ // to read without waiting for another `SIGIO`.
// The only way to know there is no more data on a socket's receive buffer is
// when `read` returns `EAGAIN` which is handled when `ReadFromSocket` returns
// a negative value.
@@ -1252,10 +1253,15 @@ void CommunicationThread(int Socket){
return;
}
+ ASSERT(Connection->ThreadID == gettid());
Connection->Connect(Socket);
Connection->WaitingForACK = false;
- if(fcntl(Socket, F_SETOWN, getpid()) == -1){
- error("CommunicationThread: F_SETOWN fehlgeschlagen für Socket %d.\n", Socket);
+
+ struct f_owner_ex FOwnerEx = {};
+ FOwnerEx.type = F_OWNER_TID;
+ FOwnerEx.pid = Connection->ThreadID;
+ if(fcntl(Socket, F_SETOWN_EX, &FOwnerEx) == -1){
+ error("CommunicationThread: F_SETOWN_EX fehlgeschlagen für Socket %d.\n", Socket);
if(close(Socket) == -1){
error("CommunicationThread: Fehler %d beim Schließen der Socket (2).\n", errno);
}
@@ -1275,7 +1281,14 @@ void CommunicationThread(int Socket){
sigset_t SignalSet;
sigfillset(&SignalSet);
sigprocmask(SIG_SETMASK, &SignalSet, NULL);
- alarm(5);
+ if(!Connection->SetLoginTimer(5)){
+ error("CommunicationThread: Failed to set login timer.\n");
+ if(close(Socket) == -1){
+ error("CommunicationThread: Fehler %d beim Schließen der Socket (4).\n", errno);
+ }
+ Connection->Free();
+ return;
+ }
if(!ReceiveCommand(Connection)){
Connection->Close(true);
@@ -1315,7 +1328,8 @@ void CommunicationThread(int Socket){
}
case SIGALRM:{
- // NOTE(fusion): Login deadline.
+ // NOTE(fusion): Login timeout.
+ Connection->StopLoginTimer();
if(Connection->State == CONNECTION_CONNECTED){
print(2, "Login-TimeOut für Socket %d.\n", Socket);
Connection->Close(false);
@@ -1380,7 +1394,7 @@ int HandleConnection(void *Data){
// =============================================================================
bool OpenSocket(void){
print(1, "Starte Game-Server...\n");
- print(1, "Pid %d - horche an Port %d\n", getpid(), GamePort);
+ print(1, "Pid=%d, Tid=%d - horche an Port %d\n", getpid(), gettid(), GamePort);
TCPSocket = socket(AF_INET, SOCK_STREAM, 0);
if(TCPSocket == -1){
error("LaunchServer: Kann Socket nicht öffnen.\n");
@@ -1422,7 +1436,7 @@ bool OpenSocket(void){
}
int AcceptorThreadLoop(void *Unused){
- AcceptorThreadPID = getpid();
+ AcceptorThreadID = gettid();
print(1, "Warte auf Clients...\n");
while(GameRunning()){
int Socket = accept(TCPSocket, NULL, NULL);
@@ -1485,7 +1499,7 @@ int AcceptorThreadLoop(void *Unused){
}
}
- AcceptorThreadPID = 0;
+ AcceptorThreadID = 0;
if(ActiveConnections > 0){
print(3, "Warte auf Beendigung von %d Communication-Threads...\n", ActiveConnections);
while(ActiveConnections > 0){
@@ -1518,7 +1532,7 @@ void InitCommunication(void){
WaitinglistHead = NULL;
TCPSocket = -1;
AcceptorThread = INVALID_THREAD_HANDLE;
- AcceptorThreadPID = 0;
+ AcceptorThreadID = 0;
ActiveConnections = 0;
QueryManagerConnectionPool.init();
@@ -1539,10 +1553,12 @@ void InitCommunication(void){
}
void ExitCommunication(void){
+ // NOTE(fusion): `SIGHUP` is used to signal the connection thread to close
+ // the connection and terminate.
print(3, "Beende alle Verbindungen...\n");
TConnection *Connection = GetFirstConnection();
while(Connection != NULL){
- kill(Connection->GetPID(), SIGHUP);
+ tgkill(GetGameProcessID(), Connection->GetThreadID(), SIGHUP);
Connection = GetNextConnection();
}
@@ -1557,8 +1573,8 @@ void ExitCommunication(void){
}
if(AcceptorThread != INVALID_THREAD_HANDLE){
- if(AcceptorThreadPID != 0){
- kill(AcceptorThreadPID, SIGHUP);
+ if(AcceptorThreadID != 0){
+ tgkill(GetGameProcessID(), AcceptorThreadID, SIGHUP);
}
JoinThread(AcceptorThread);
AcceptorThread = INVALID_THREAD_HANDLE;
diff --git a/src/connections.cc b/src/connections.cc
index cef9fa6..84daec6 100644
--- a/src/connections.cc
+++ b/src/connections.cc
@@ -79,13 +79,64 @@ void TConnection::EmergencyPing(void){
}
}
-pid_t TConnection::GetPID(void){
+pid_t TConnection::GetThreadID(void){
if(this->State == CONNECTION_FREE){
- error("TConnection::GetPID: Verbindung ist nicht zugewiesen.\n");
+ error("TConnection::GetThreadID: Verbindung ist nicht zugewiesen.\n");
return 0;
}
- return this->PID;
+ return this->ThreadID;
+}
+
+bool TConnection::SetLoginTimer(int Timeout){
+ if(this->State == CONNECTION_FREE){
+ error("TConnection::SetLoginTimer: Verbindung ist nicht zugewiesen.\n");
+ return false;
+ }
+
+ if(this->LoginTimer != 0){
+ error("TConnection::SetLoginTimer: Timer already set.\n");
+ return false;
+ }
+
+ struct sigevent SigEvent = {};
+ SigEvent.sigev_notify = SIGEV_THREAD_ID;
+ SigEvent.sigev_signo = SIGALRM;
+ SigEvent.sigev_notify_thread_id = this->ThreadID;
+ if(timer_create(CLOCK_MONOTONIC, &SigEvent, &this->LoginTimer) == -1){
+ error("TConnection::SetLoginTimer: Failed to create timer: (%d) %s\n",
+ errno, strerrordesc_np(errno));
+ return false;
+ }
+
+ struct itimerspec TimerSpec = {};
+ TimerSpec.it_value.tv_sec = Timeout;
+ if(timer_settime(this->LoginTimer, 0, &TimerSpec, NULL) == -1){
+ error("TConnection::SetLoginTimer: Failed to start timer: (%d) %s\n",
+ errno, strerrordesc_np(errno));
+ return false;
+ }
+
+ return true;
+}
+
+void TConnection::StopLoginTimer(void){
+ if(this->State == CONNECTION_FREE){
+ error("TConnection::SetLoginTimer: Verbindung ist nicht zugewiesen.\n");
+ return;
+ }
+
+ if(this->LoginTimer == 0){
+ error("TConnection::StopLoginTimer: Timer not set.\n");
+ return;
+ }
+
+ if(timer_delete(this->LoginTimer) == -1){
+ error("TConnection::StopLoginTimer: Failed to delete timer: (%d) %s\n",
+ errno, strerrordesc_np(errno));
+ }
+
+ this->LoginTimer = 0;
}
int TConnection::GetSocket(void){
@@ -116,7 +167,8 @@ void TConnection::Assign(void){
}
this->State = CONNECTION_ASSIGNED;
- this->PID = getpid();
+ this->ThreadID = gettid();
+ this->LoginTimer = 0;
}
void TConnection::Connect(int Socket){
@@ -264,7 +316,7 @@ void TConnection::Disconnect(void){
this->ClearKnownCreatureTable(true);
this->ConnectionIsOk = false;
this->State = CONNECTION_DISCONNECTED;
- kill(this->PID, SIGHUP);
+ tgkill(GetGameProcessID(), this->ThreadID, SIGHUP);
}
TPlayer *TConnection::GetPlayer(void){
diff --git a/src/connections.hh b/src/connections.hh
index d4f0755..5489d18 100644
--- a/src/connections.hh
+++ b/src/connections.hh
@@ -154,7 +154,9 @@ struct TConnection {
void Process(void);
void ResetTimer(int Command);
void EmergencyPing(void);
- pid_t GetPID(void);
+ pid_t GetThreadID(void);
+ bool SetLoginTimer(int Timeout);
+ void StopLoginTimer(void);
int GetSocket(void);
const char *GetIPAddress(void);
void Free(void);
@@ -203,7 +205,8 @@ struct TConnection {
TConnection *NextSendingConnection;
uint32 RandomSeed;
CONNECTIONSTATE State;
- pid_t PID;
+ pid_t ThreadID;
+ timer_t LoginTimer;
int Socket;
char IPAddress[16];
TXTEASymmetricKey SymmetricKey;
diff --git a/src/crplayer.cc b/src/crplayer.cc
index dce7ad2..8ecc926 100644
--- a/src/crplayer.cc
+++ b/src/crplayer.cc
@@ -732,7 +732,7 @@ void TPlayer::TakeOver(TConnection *Connection){
return;
}
- if(PlayerData->Locked != getpid()){
+ if(PlayerData->Locked != gettid()){
error("TPlayer::TakeOver: PlayerData-Slot ist nicht korrekt gesperrt (%d).\n", PlayerData->Locked);
}
@@ -2639,7 +2639,7 @@ void FreePlayerPoolSlot(TPlayerData *Slot){
return;
}
- if(Slot->Locked != 0 && Slot->Locked != getpid()){
+ if(Slot->Locked != 0 && Slot->Locked != gettid()){
error("FreePlayerPoolSlot: Slot ist von einem anderen Thread gesperrt.\n");
return;
}
@@ -2693,7 +2693,7 @@ TPlayerData *AssignPlayerPoolSlot(uint32 CharacterID, bool DontWait){
}
if(Slot->Locked == 0){
- Slot->Locked = getpid();
+ Slot->Locked = gettid();
PlayerDataPoolMutex.up();
return Slot;
}
@@ -2753,7 +2753,7 @@ TPlayerData *AssignPlayerPoolSlot(uint32 CharacterID, bool DontWait){
memset(Slot, 0, sizeof(TPlayerData));
Slot->CharacterID = CharacterID;
- Slot->Locked = getpid();
+ Slot->Locked = gettid();
PlayerDataPoolMutex.up();
print(3, "Lade Daten für Spieler %u.\n", CharacterID);
@@ -2792,7 +2792,7 @@ TPlayerData *AttachPlayerPoolSlot(uint32 CharacterID, bool DontWait){
}
if(Slot->Locked == 0){
- Slot->Locked = getpid();
+ Slot->Locked = gettid();
PlayerDataPoolMutex.up();
return Slot;
}
@@ -2817,7 +2817,7 @@ void AttachPlayerPoolSlot(TPlayerData *Slot, bool DontWait){
while(true){
PlayerDataPoolMutex.down();
if(Slot->Locked == 0){
- Slot->Locked = getpid();
+ Slot->Locked = gettid();
PlayerDataPoolMutex.up();
return;
}
@@ -2882,7 +2882,7 @@ void ReleasePlayerPoolSlot(TPlayerData *Slot){
return;
}
- if(Slot->Locked != getpid()){
+ if(Slot->Locked != gettid()){
error("ReleasePlayerPoolSlot: Slot ist von einem anderen Thread gesperrt.\n");
return;
}
@@ -2908,7 +2908,7 @@ void SavePlayerPoolSlots(void){
}
AttachPlayerPoolSlot(Slot, true);
- if(Slot->Locked == getpid()){
+ if(Slot->Locked == gettid()){
SavePlayerPoolSlot(Slot);
ReleasePlayerPoolSlot(Slot);
}
diff --git a/src/main.cc b/src/main.cc
index b838544..ddeda8b 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -14,39 +14,55 @@
#include <signal.h>
#include <sys/stat.h>
-#include <sys/time.h>
#include <fstream>
static bool BeADaemon = false;
static bool Reboot = false;
static bool SaveMapOn = false;
+static timer_t BeatTimer;
static int SigAlarmCounter = 0;
static int SigUsr1Counter = 0;
-static sighandler_t handler(int signr, sighandler_t sighandler){
- struct sigaction act;
- struct sigaction oldact;
+static sighandler_t SigHandler(int SigNr, sighandler_t Handler){
+ struct sigaction Action;
+ struct sigaction OldAction;
- act.sa_handler = sighandler;
+ Action.sa_handler = Handler;
// TODO(fusion): I feel we should probably use `sigfillset` specially for
// signals that share the same handler.
- sigemptyset(&act.sa_mask);
+ sigemptyset(&Action.sa_mask);
- if(signr == SIGALRM){
- act.sa_flags = SA_INTERRUPT;
+ if(SigNr == SIGALRM){
+ Action.sa_flags = SA_INTERRUPT;
}else{
- act.sa_flags = SA_RESTART;
+ Action.sa_flags = SA_RESTART;
}
- if(sigaction(signr, &act, &oldact) == 0){
- return oldact.sa_handler;
+ if(sigaction(SigNr, &Action, &OldAction) == 0){
+ return OldAction.sa_handler;
}else{
return SIG_ERR;
}
}
+static void SigBlock(int SigNr){
+ sigset_t Set;
+ sigemptyset(&Set);
+ sigaddset(&Set, SigNr);
+ if(sigprocmask(SIG_BLOCK, &Set, NULL) == -1){
+ error("SigBlock: Failed to block signal %d (%s): (%d) %s\n",
+ SigNr, sigdescr_np(SigNr), errno, strerrordesc_np(errno));
+ }
+}
+
+static void SigWaitAny(void){
+ sigset_t Set;
+ sigemptyset(&Set);
+ sigsuspend(&Set);
+}
+
static void SigHupHandler(int signr){
// no-op (?)
}
@@ -60,12 +76,12 @@ static void DefaultHandler(int signr){
print(1, "DefaultHandler: Beende Game-Server (SigNr. %d: %s).\n",
signr, sigdescr_np(signr));
- handler(SIGINT, SIG_IGN);
- handler(SIGQUIT, SIG_IGN);
- handler(SIGTERM, SIG_IGN);
- handler(SIGXCPU, SIG_IGN);
- handler(SIGXFSZ, SIG_IGN);
- handler(SIGPWR, SIG_IGN);
+ SigHandler(SIGINT, SIG_IGN);
+ SigHandler(SIGQUIT, SIG_IGN);
+ SigHandler(SIGTERM, SIG_IGN);
+ SigHandler(SIGXCPU, SIG_IGN);
+ SigHandler(SIGXFSZ, SIG_IGN);
+ SigHandler(SIGPWR, SIG_IGN);
SaveMapOn = (signr == SIGQUIT) || (signr == SIGTERM) || (signr == SIGPWR);
if(signr == SIGTERM){
@@ -91,55 +107,72 @@ static void ErrorHandler(int signr){
#endif
static void InitSignalHandler(void){
- int count = 0;
- count += (handler(SIGHUP, SigHupHandler) != SIG_ERR);
- count += (handler(SIGINT, DefaultHandler) != SIG_ERR);
- count += (handler(SIGQUIT, DefaultHandler) != SIG_ERR);
- count += (handler(SIGABRT, SigAbortHandler) != SIG_ERR);
- count += (handler(SIGUSR1, SIG_IGN) != SIG_ERR);
- count += (handler(SIGUSR2, SIG_IGN) != SIG_ERR);
- count += (handler(SIGPIPE, SIG_IGN) != SIG_ERR);
- count += (handler(SIGALRM, SIG_IGN) != SIG_ERR);
- count += (handler(SIGTERM, DefaultHandler) != SIG_ERR);
- count += (handler(SIGSTKFLT, SIG_IGN) != SIG_ERR);
- count += (handler(SIGCHLD, SIG_IGN) != SIG_ERR);
- count += (handler(SIGTSTP, SIG_IGN) != SIG_ERR);
- count += (handler(SIGTTIN, SIG_IGN) != SIG_ERR);
- count += (handler(SIGTTOU, SIG_IGN) != SIG_ERR);
- count += (handler(SIGURG, SIG_IGN) != SIG_ERR);
- count += (handler(SIGXCPU, DefaultHandler) != SIG_ERR);
- count += (handler(SIGXFSZ, DefaultHandler) != SIG_ERR);
- count += (handler(SIGVTALRM, SIG_IGN) != SIG_ERR);
- count += (handler(SIGWINCH, SIG_IGN) != SIG_ERR);
- count += (handler(SIGPOLL, SIG_IGN) != SIG_ERR);
- count += (handler(SIGPWR, DefaultHandler) != SIG_ERR);
- print(1, "InitSignalHandler: %d Signalhandler eingerichtet (Soll=%d)\n", count, 0x1c);
+ int Count = 0;
+ Count += (SigHandler(SIGHUP, SigHupHandler) != SIG_ERR);
+ Count += (SigHandler(SIGINT, DefaultHandler) != SIG_ERR);
+ Count += (SigHandler(SIGQUIT, DefaultHandler) != SIG_ERR);
+ Count += (SigHandler(SIGABRT, SigAbortHandler) != SIG_ERR);
+ Count += (SigHandler(SIGUSR1, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGUSR2, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGPIPE, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGALRM, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGTERM, DefaultHandler) != SIG_ERR);
+ Count += (SigHandler(SIGSTKFLT, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGCHLD, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGTSTP, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGTTIN, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGTTOU, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGURG, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGXCPU, DefaultHandler) != SIG_ERR);
+ Count += (SigHandler(SIGXFSZ, DefaultHandler) != SIG_ERR);
+ Count += (SigHandler(SIGVTALRM, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGWINCH, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGPOLL, SIG_IGN) != SIG_ERR);
+ Count += (SigHandler(SIGPWR, DefaultHandler) != SIG_ERR);
+ print(1, "InitSignalHandler: %d Signalhandler eingerichtet (Soll=%d)\n", Count, 0x1c);
}
static void ExitSignalHandler(void){
// no-op
}
-static void SigAlarmHandler(int signr){
- SigAlarmCounter += 1;
-
- struct itimerval new_timer = {};
- struct itimerval old_timer = {};
- new_timer.it_value.tv_usec = Beat * 1000;
- setitimer(ITIMER_REAL, &new_timer, &old_timer);
+static void SigAlarmHandler(int SigNr){
+ SigAlarmCounter += (1 + timer_getoverrun(BeatTimer));
}
static void InitTime(void){
+ ASSERT(Beat > 0);
SigAlarmCounter = 0;
- handler(SIGALRM, SigAlarmHandler);
- SigAlarmHandler(SIGALRM);
+ SigHandler(SIGALRM, SigAlarmHandler);
+
+ struct sigevent SigEvent = {};
+ SigEvent.sigev_notify = SIGEV_THREAD_ID;
+ SigEvent.sigev_signo = SIGALRM;
+ SigEvent.sigev_notify_thread_id = gettid();
+ if(timer_create(CLOCK_MONOTONIC, &SigEvent, &BeatTimer) == -1){
+ error("InitTime: Failed to create beat timer: (%d) %s\n",
+ errno, strerrordesc_np(errno));
+ throw "cannot create beat timer";
+ }
+
+ struct itimerspec TimerSpec = {};
+ TimerSpec.it_interval.tv_sec = Beat / 1000;
+ TimerSpec.it_interval.tv_nsec = (Beat % 1000) * 1000000;
+ TimerSpec.it_value = TimerSpec.it_interval;
+ if(timer_settime(BeatTimer, 0, &TimerSpec, NULL) == -1){
+ error("InitTime: Failed to start beat timer: (%d) %s\n",
+ errno, strerrordesc_np(errno));
+ throw "cannot start beat timer";
+ }
}
static void ExitTime(void){
- struct itimerval new_timer = {};
- struct itimerval old_timer = {};
- setitimer(ITIMER_REAL, &new_timer, &old_timer);
- handler(SIGALRM, SIG_IGN);
+ if(timer_delete(BeatTimer) == -1){
+ error("ExitTime: Failed to delete beat timer: (%d) %s\n",
+ errno, strerrordesc_np(errno));
+ }
+
+ SigHandler(SIGALRM, SIG_IGN);
}
static void UnlockGame(void){
@@ -178,7 +211,7 @@ static void LockGame(void){
{
std::ofstream OutputFile(FileName, std::ios_base::out | std::ios_base::trunc);
- OutputFile << (int)getpid();
+ OutputFile << getpid();
}
atexit(UnlockGame);
@@ -420,28 +453,29 @@ static void SigUsr1Handler(int signr){
}
static void LaunchGame(void){
+ SaveMapOn = true;
SigUsr1Counter = 0;
- handler(SIGUSR1, SigUsr1Handler);
+ SigAlarmCounter = 0;
+
+ SigBlock(SIGUSR1);
+ SigHandler(SIGUSR1, SigUsr1Handler);
StartGame();
- print(1, "LaunchGame: Game-Server ist bereit (Pid=%d).\n", getpid());
+ print(1, "LaunchGame: Game-Server ist bereit (Pid=%d, Tid=%d).\n", getpid(), gettid());
- // IMPORTANT(fusion): The whole design of the server is to run across a few
- // different processes and communicate via shared memory and signals. Each
- // of these processes are single threaded, meaning that signal handlers are
- // executed concurrently on the SAME thread. You'd still require to synchronize
- // access to large structures to avoid race conditions BUT accessing a few
- // independent integers and booleans like we're doing with `SigAlarmCounter`,
- // `SigUsr1Counter`, and `SaveMapOn` is perfectly safe.
+ // IMPORTANT(fusion): In general signal handlers can execute on any thread in
+ // the process group but the design of the server is to use signals directed
+ // at different threads to communicate certain events (see `CommunicationThread`
+ // for example).
+ // Even if that wasn't the case, loads/stores on x86 are always ATOMIC and when
+ // there is a single writer (signal handlers), even a read-modify-write will be
+ // atomic.
+ // This is to say, there should be no problem with reading from `SigUsr1Counter`,
+ // `SigAlarmCounter`, or `SaveMapOn`, which may be modified from signal handlers.
- SaveMapOn = true;
- SigAlarmCounter = 0;
while(GameRunning()){
- // TODO(fusion): `sigblock` and `sigpause` are deprecated in favour of
- // `sigprocmask` and `sigsuspend`.
- sigblock(sigmask(SIGUSR1));
while(SigUsr1Counter == 0 && SigAlarmCounter == 0){
- sigpause(0);
+ SigWaitAny();
}
if(SigUsr1Counter > 0){
diff --git a/src/moveuse.cc b/src/moveuse.cc
index cd03bd8..81fa948 100644
--- a/src/moveuse.cc
+++ b/src/moveuse.cc
@@ -861,7 +861,7 @@ void SendMails(TPlayerData *PlayerData){
return;
}
- if(PlayerData->Locked != getpid()){
+ if(PlayerData->Locked != gettid()){
error("SendMails: Slot ist nicht vom GameThread gesperrt.\n");
return;
}
diff --git a/src/reader.cc b/src/reader.cc
index 09d739b..a29b35b 100644
--- a/src/reader.cc
+++ b/src/reader.cc
@@ -135,11 +135,11 @@ void ProcessLoadCharacterOrder(uint32 CharacterID){
break;
}
- if(Slot->Locked == GetGameThreadPID()){
+ if(Slot->Locked == GetGameThreadID()){
break;
}
- if(Slot->Locked == getpid()){
+ if(Slot->Locked == gettid()){
IncreasePlayerPoolSlotSticky(Slot);
ReleasePlayerPoolSlot(Slot);
CharacterReply(CharacterID);
diff --git a/src/receiving.cc b/src/receiving.cc
index 147f085..6cf6609 100644
--- a/src/receiving.cc
+++ b/src/receiving.cc
@@ -1800,9 +1800,12 @@ void ReceiveData(void){
if(Connection->Live() && Connection->WaitingForACK){
ReceiveData(Connection);
Connection->WaitingForACK = false;
- // TODO(fusion): It could have been disconnected inside `ReceiveData`?
+ // NOTE(fusion): `SIGUSR1` is used to signal the connection thread
+ // that we parsed all received data and that it may resume reading.
+ // We check if the connection is still live because it may have been
+ // disconnected inside `ReceiveData`.
if(Connection->Live()){
- kill(Connection->GetPID(), SIGUSR1);
+ tgkill(GetGameProcessID(), Connection->GetThreadID(), SIGUSR1);
}
}
Connection = GetNextConnection();
diff --git a/src/sending.cc b/src/sending.cc
index 970439f..3b22cc3 100644
--- a/src/sending.cc
+++ b/src/sending.cc
@@ -20,8 +20,10 @@ void SendAll(void){
while(Connection != NULL){
if(Connection->WillingToSend){
Connection->WillingToSend = false;
+ // NOTE(fusion): `SIGUSR2` is used to signal the connection thread
+ // that there is pending data in the connection's output buffer.
if(Connection->Live() && Connection->NextToCommit > Connection->NextToSend){
- kill(Connection->GetPID(), SIGUSR2);
+ tgkill(GetGameProcessID(), Connection->GetThreadID(), SIGUSR2);
}
}else{
error("SendAll: Verbindung ist nicht sendewillig.\n");
@@ -274,6 +276,7 @@ void SendMapPoint(TConnection *Connection, int x, int y, int z){
while(Obj != NONE && ObjCount < MAX_OBJECTS_PER_POINT){
SendMapObject(Connection, Obj);
Obj = Obj.getNextObject();
+ ObjCount += 1;
}
}
Skip += 1;
@@ -445,7 +448,7 @@ void SendFullScreen(TConnection *Connection){
SendByte(Connection, (uint8)PlayerZ);
Skip = -1;
- for(int PointZ = StartZ; PointZ != EndZ; EndZ += StepZ){
+ for(int PointZ = StartZ; PointZ != EndZ; PointZ += StepZ){
int ZOffset = (PlayerZ - PointZ);
for(int PointX = MinX; PointX <= MaxX; PointX += 1)
for(int PointY = MinY; PointY <= MaxY; PointY += 1){
@@ -499,7 +502,7 @@ void SendRow(TConnection *Connection, int Direction){
}
Skip = -1;
- for(int PointZ = StartZ; PointZ != EndZ; EndZ += StepZ){
+ for(int PointZ = StartZ; PointZ != EndZ; PointZ += StepZ){
int ZOffset = (PlayerZ - PointZ);
for(int PointX = MinX; PointX <= MaxX; PointX += 1)
for(int PointY = MinY; PointY <= MaxY; PointY += 1){
@@ -561,7 +564,7 @@ void SendFloors(TConnection *Connection, bool Up){
int MaxY = MinY + Connection->TerminalHeight - 1;
Skip = -1;
- for(int PointZ = StartZ; PointZ != EndZ; EndZ += StepZ){
+ for(int PointZ = StartZ; PointZ != EndZ; PointZ += StepZ){
int ZOffset = (PlayerZ - PointZ);
for(int PointX = MinX; PointX <= MaxX; PointX += 1)
for(int PointY = MinY; PointY <= MaxY; PointY += 1){
diff --git a/src/shm.cc b/src/shm.cc
index 7a63eae..98dd526 100644
--- a/src/shm.cc
+++ b/src/shm.cc
@@ -23,7 +23,8 @@ struct TSharedMemory {
int PrintBufferPosition;
char PrintBuffer[200][128];
GAMESTATE GameState;
- pid_t GameThreadPID;
+ pid_t GameProcessID;
+ pid_t GameThreadID;
};
static TSharedMemory *SHM = NULL;
@@ -101,10 +102,18 @@ bool GameEnding(void){
return Result;
}
-pid_t GetGameThreadPID(void){
+pid_t GetGameProcessID(void){
pid_t Pid = 0;
if(SHM != NULL){
- Pid = SHM->GameThreadPID;
+ Pid = SHM->GameProcessID;
+ }
+ return Pid;
+}
+
+pid_t GetGameThreadID(void){
+ pid_t Pid = 0;
+ if(SHM != NULL){
+ Pid = SHM->GameThreadID;
}
return Pid;
}
@@ -372,7 +381,8 @@ void InitSHM(bool Verbose){
SHM->PrintBufferPosition = 1;
SHM->GameState = GAME_STARTING;
- SHM->GameThreadPID = getpid();
+ SHM->GameProcessID = getpid();
+ SHM->GameThreadID = gettid();
}
void ExitSHM(void){