diff options
| -rw-r--r-- | TODO.md | 5 | ||||
| -rw-r--r-- | src/common.hh | 13 | ||||
| -rw-r--r-- | src/communication.cc | 58 | ||||
| -rw-r--r-- | src/connections.cc | 62 | ||||
| -rw-r--r-- | src/connections.hh | 7 | ||||
| -rw-r--r-- | src/crplayer.cc | 16 | ||||
| -rw-r--r-- | src/main.cc | 172 | ||||
| -rw-r--r-- | src/moveuse.cc | 2 | ||||
| -rw-r--r-- | src/reader.cc | 4 | ||||
| -rw-r--r-- | src/receiving.cc | 7 | ||||
| -rw-r--r-- | src/sending.cc | 11 | ||||
| -rw-r--r-- | src/shm.cc | 18 |
12 files changed, 248 insertions, 127 deletions
@@ -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){ @@ -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){ |
