diff options
author | Clément Gallet <clement.gallet@ens-lyon.org> | 2020-07-10 13:29:56 +0200 |
---|---|---|
committer | Clément Gallet <clement.gallet@ens-lyon.org> | 2020-07-10 13:29:56 +0200 |
commit | fa4e69dbcbf6db26be6f60fcb6a3bcd1fdc8f3f1 (patch) | |
tree | 64116520e1c12230503341a2c465a4ba15ef51ab | |
parent | e96250eac2fa1c65ee4f9cce28b52e7ca9f760ae (diff) |
Check for forked savestates to completefork
-rw-r--r-- | src/library/checkpoint/Checkpoint.cpp | 29 | ||||
-rw-r--r-- | src/library/checkpoint/Checkpoint.h | 4 | ||||
-rw-r--r-- | src/library/checkpoint/ReservedMemory.h | 6 | ||||
-rw-r--r-- | src/library/checkpoint/SaveStateManager.cpp | 94 | ||||
-rw-r--r-- | src/library/checkpoint/SaveStateManager.h | 40 | ||||
-rw-r--r-- | src/library/checkpoint/ThreadManager.cpp | 1 | ||||
-rw-r--r-- | src/library/checkpoint/ThreadManager.h | 3 | ||||
-rw-r--r-- | src/library/frame.cpp | 63 | ||||
-rw-r--r-- | src/program/GameLoop.cpp | 21 |
9 files changed, 187 insertions, 74 deletions
diff --git a/src/library/checkpoint/Checkpoint.cpp b/src/library/checkpoint/Checkpoint.cpp index 6006f752..6d5631fe 100644 --- a/src/library/checkpoint/Checkpoint.cpp +++ b/src/library/checkpoint/Checkpoint.cpp @@ -143,10 +143,10 @@ static void setPagesFd(int index, int fd) pages[index] = fd; } -bool Checkpoint::checkCheckpoint() +int Checkpoint::checkCheckpoint() { if (shared_config.savestate_settings & SharedConfig::SS_RAM) - return true; + return 0; /* Get an estimation of the savestate space */ uint64_t savestate_size = 0; @@ -179,14 +179,14 @@ bool Checkpoint::checkCheckpoint() #ifdef LIBTAS_ENABLE_HUD RenderHUD::insertMessage("Not enough available space to store the savestate"); #endif - return false; + return SaveStateManager::ESTATE_NOMEM; } } - return true; + return 0; } -bool Checkpoint::checkRestore() +int Checkpoint::checkRestore() { /* Check that the savestate files exist */ if (shared_config.savestate_settings & SharedConfig::SS_RAM) { @@ -195,7 +195,7 @@ bool Checkpoint::checkRestore() #ifdef LIBTAS_ENABLE_HUD RenderHUD::insertMessage("Savestate does not exist"); #endif - return false; + return SaveStateManager::ESTATE_NOSTATE; } if (!getPagesFd(ss_index)) { @@ -203,7 +203,7 @@ bool Checkpoint::checkRestore() #ifdef LIBTAS_ENABLE_HUD RenderHUD::insertMessage("Savestate does not exist"); #endif - return false; + return SaveStateManager::ESTATE_NOSTATE; } } else { @@ -213,14 +213,14 @@ bool Checkpoint::checkRestore() #ifdef LIBTAS_ENABLE_HUD RenderHUD::insertMessage("Savestate does not exist"); #endif - return false; + return SaveStateManager::ESTATE_NOSTATE; } if (stat(pagespath, &sb) == -1) { debuglogstdio(LCF_CHECKPOINT | LCF_ERROR, "Savestate does not exist"); #ifdef LIBTAS_ENABLE_HUD RenderHUD::insertMessage("Savestate does not exist"); #endif - return false; + return SaveStateManager::ESTATE_NOSTATE; } } @@ -232,7 +232,7 @@ bool Checkpoint::checkRestore() else { NATIVECALL(pmfd = open(pagemappath, O_RDONLY)); if (pmfd == -1) - return false; + return SaveStateManager::ESTATE_NOSTATE; } /* Read the savestate header */ @@ -264,7 +264,7 @@ bool Checkpoint::checkRestore() #ifdef LIBTAS_ENABLE_HUD RenderHUD::insertMessage("Loading the savestate not allowed because new threads were created"); #endif - return false; + return SaveStateManager::ESTATE_NOTSAMETHREADS; } } @@ -273,10 +273,10 @@ bool Checkpoint::checkRestore() #ifdef LIBTAS_ENABLE_HUD RenderHUD::insertMessage("Loading the savestate not allowed because new threads were created"); #endif - return false; + return SaveStateManager::ESTATE_NOTSAMETHREADS; } - return true; + return SaveStateManager::ESTATE_OK; } void Checkpoint::handler(int signum) @@ -1011,7 +1011,8 @@ static size_t writeAllAreas(bool base) /* Store that we are the child, so that destructors may act differently */ ThreadManager::setChildFork(); - exit(0); + /* Return the savestate index as status code */ + exit(ss_index); } return savestate_size; diff --git a/src/library/checkpoint/Checkpoint.h b/src/library/checkpoint/Checkpoint.h index 58c1d6cc..06944cda 100644 --- a/src/library/checkpoint/Checkpoint.h +++ b/src/library/checkpoint/Checkpoint.h @@ -35,8 +35,8 @@ namespace Checkpoint void setCurrentToParent(); - bool checkCheckpoint(); - bool checkRestore(); + int checkCheckpoint(); + int checkRestore(); void handler(int signum); }; } diff --git a/src/library/checkpoint/ReservedMemory.h b/src/library/checkpoint/ReservedMemory.h index 7364674d..2d9eb051 100644 --- a/src/library/checkpoint/ReservedMemory.h +++ b/src/library/checkpoint/ReservedMemory.h @@ -33,12 +33,14 @@ namespace ReservedMemory { enum Addresses { PAGEMAPS_ADDR = 0, PAGES_ADDR = 11*sizeof(int), - PSM_ADDR = 22*sizeof(int), + SS_SLOTS_ADDR = 22*sizeof(int), + PSM_ADDR = 22*sizeof(int)+11*sizeof(bool), STACK_ADDR = ONE_MB, }; enum Sizes { PAGEMAPS_SIZE = PAGES_ADDR - PAGEMAPS_ADDR, - PAGES_SIZE = PSM_ADDR - PAGES_ADDR, + PAGES_SIZE = SS_SLOTS_ADDR - PAGES_ADDR, + SS_SLOTS_SIZE = PSM_ADDR - SS_SLOTS_ADDR, PSM_SIZE = STACK_ADDR - PSM_ADDR, STACK_SIZE = RESTORE_TOTAL_SIZE - STACK_ADDR, }; diff --git a/src/library/checkpoint/SaveStateManager.cpp b/src/library/checkpoint/SaveStateManager.cpp index 399a088a..6cb13d39 100644 --- a/src/library/checkpoint/SaveStateManager.cpp +++ b/src/library/checkpoint/SaveStateManager.cpp @@ -24,6 +24,7 @@ #include <algorithm> // std::find #include <sys/mman.h> #include <sys/syscall.h> // syscall, SYS_gettid +#include <sys/wait.h> // waitpid #include "SaveStateManager.h" #include "ThreadManager.h" @@ -46,6 +47,7 @@ static volatile bool restoreInProgress = false; static int numThreads; static int sig_suspend_threads = SIGXFSZ; static int sig_checkpoint = SIGSYS; +static bool* state_dirty; int SaveStateManager::sigCheckpoint() { @@ -63,6 +65,9 @@ void SaveStateManager::init() sem_init(&semWaitForCkptThreadSignal, 0, 0); ReservedMemory::init(); + + state_dirty = static_cast<bool*>(ReservedMemory::getAddr(ReservedMemory::SS_SLOTS_ADDR)); + memset(state_dirty, 0, 11*sizeof(bool)); } void SaveStateManager::initCheckpointThread() @@ -117,8 +122,53 @@ void SaveStateManager::initThreadFromChild(ThreadInfo* thread) } } -bool SaveStateManager::checkpoint() +int SaveStateManager::waitChild() +{ + if (!(shared_config.savestate_settings & SharedConfig::SS_FORK)) + return -1; + + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + if (pid <= 0) { + return -1; + } + status = WEXITSTATUS(status); + if ((status < 0) && (status > 10)) { + debuglog(LCF_THREAD | LCF_CHECKPOINT | LCF_ERROR, "Got unknown status code ", status, " from pid ", pid); + return -1; + } + if (!state_dirty[status]) { + debuglog(LCF_THREAD | LCF_CHECKPOINT | LCF_ERROR, "State saving ", status, " completed but was already ready"); + return -1; + } + + state_dirty[status] = false; + return status; +} + +bool SaveStateManager::stateReady(int slot) +{ + if (!(shared_config.savestate_settings & SharedConfig::SS_FORK)) + return true; + + if ((slot < 0) && (slot > 10)) { + debuglog(LCF_THREAD | LCF_CHECKPOINT | LCF_ERROR, "Wrong slot number"); + return false; + } + return !state_dirty[slot]; +} + +void SaveStateManager::stateStatus(int slot, bool dirty) { + if (shared_config.savestate_settings & SharedConfig::SS_FORK) + state_dirty[slot] = dirty; +} + +int SaveStateManager::checkpoint(int slot) +{ + if (!stateReady(slot)) + return ESTATE_NOTCOMPLETE; + ThreadInfo *current_thread = ThreadManager::getCurrentThread(); MYASSERT(current_thread->state == ThreadInfo::ST_CKPNTHREAD) @@ -132,9 +182,10 @@ bool SaveStateManager::checkpoint() AudioPlayer::close(); /* Perform a series of checks before attempting to checkpoint */ - if (!Checkpoint::checkCheckpoint()) { + int ret = Checkpoint::checkCheckpoint(); + if (ret < 0) { ThreadSync::releaseLocks(); - return false; + return ret; } /* We save the alternate stack if the game did set one */ @@ -187,11 +238,18 @@ bool SaveStateManager::checkpoint() ThreadSync::releaseLocks(); - return true; + /* Mark the savestate as dirty in case of fork savestate */ + if (!isLoading()) + stateStatus(slot, true); + + return ESTATE_OK; } -void SaveStateManager::restore() +int SaveStateManager::restore(int slot) { + if (!stateReady(slot)) + return ESTATE_NOTCOMPLETE; + ThreadInfo *current_thread = ThreadManager::getCurrentThread(); MYASSERT(current_thread->state == ThreadInfo::ST_CKPNTHREAD) ThreadSync::acquireLocks(); @@ -202,9 +260,10 @@ void SaveStateManager::restore() AudioPlayer::close(); /* Perform a series of checks before attempting to restore */ - if (!Checkpoint::checkRestore()) { + int ret = Checkpoint::checkRestore(); + if (ret < 0) { ThreadSync::releaseLocks(); - return; + return ret; } /* We save the alternate stack if the game did set one */ @@ -254,6 +313,8 @@ void SaveStateManager::restore() waitForAllRestored(current_thread); ThreadSync::releaseLocks(); + + return ESTATE_UNKNOWN; } void SaveStateManager::suspendThreads() @@ -488,6 +549,25 @@ void SaveStateManager::waitForAllRestored(ThreadInfo *thread) } } +void SaveStateManager::printError(int err) +{ + const char* const errors[] = { + "No error", + "Savestate failed", + "Not enough available space to store the savestate", + "Savestate does not exist", + "Loading not allowed because new threads were created", + "State still saving", + 0 }; + + if (err < 0) { + debuglogstdio(LCF_CHECKPOINT | LCF_ERROR, errors[-err]); +#ifdef LIBTAS_ENABLE_HUD + RenderHUD::insertMessage(errors[-err]); +#endif + } +} + bool SaveStateManager::isLoading() { return restoreInProgress; diff --git a/src/library/checkpoint/SaveStateManager.h b/src/library/checkpoint/SaveStateManager.h index 43d757db..1836e30e 100644 --- a/src/library/checkpoint/SaveStateManager.h +++ b/src/library/checkpoint/SaveStateManager.h @@ -33,21 +33,15 @@ namespace libtas { namespace SaveStateManager { -// static thread_local ThreadInfo* current_thread; -// -// static pthread_mutex_t threadStateLock; -// static pthread_mutex_t threadListLock; -// static pthread_rwlock_t threadResumeLock; -// static sem_t semNotifyCkptThread; -// static sem_t semWaitForCkptThreadSignal; -// -// static int numThreads; -// -// /* Which signals are we using */ -// int sig_suspend_threads; -// int sig_checkpoint; -// -// volatile bool restoreInProgress; +/* List of error codes */ +enum Error { + ESTATE_OK = 0, + ESTATE_UNKNOWN = -1, // Unknown error + ESTATE_NOMEM = -2, // Not enough memory to perform savestate + ESTATE_NOSTATE = -3, // No state in slot + ESTATE_NOTSAMETHREADS = -4, // Thread list has changed + ESTATE_NOTCOMPLETE = -5, // State still being saved +}; void init(); @@ -56,11 +50,20 @@ void initCheckpointThread(); void initThreadFromChild(ThreadInfo* thread); +/* Wait for a child to terminate and register the savestate slot */ +int waitChild(); + +/* Returns if a state is completed (useful for fork savestates) */ +bool stateReady(int slot); + +/* Change the dirty state of savestate when doing forked savestate */ +void stateStatus(int slot, bool dirty); + /* Save a savestate and returns if succeeded */ -bool checkpoint(); +int checkpoint(int slot); /* Restore a savestate */ -void restore(); +int restore(int slot); /* Send a signal to suspend all threads before checkpointing */ void suspendThreads(); @@ -79,6 +82,9 @@ bool isLoading(); /* Restore the state of loading a savestate, after memory has been rewritten */ void setLoading(); +/* Print savestate error and display it on HUD */ +void printError(int err); + /* Returns the signal number of checkpoint and thread suspend */ int sigCheckpoint(); int sigSuspend(); diff --git a/src/library/checkpoint/ThreadManager.cpp b/src/library/checkpoint/ThreadManager.cpp index ad0ce6e7..095ed9d1 100644 --- a/src/library/checkpoint/ThreadManager.cpp +++ b/src/library/checkpoint/ThreadManager.cpp @@ -182,7 +182,6 @@ pid_t ThreadManager::getThreadTid(pthread_t pthread_id) void ThreadManager::restoreThreadTids() { - /* Restore tid in all threads */ for (ThreadInfo* thread = thread_list; thread != nullptr; thread = thread->next) { int* thread_data = reinterpret_cast<int*>(thread->pthread_id); thread_data[offset_tid] = thread->tid; diff --git a/src/library/checkpoint/ThreadManager.h b/src/library/checkpoint/ThreadManager.h index a4b0fe91..c3ce629a 100644 --- a/src/library/checkpoint/ThreadManager.h +++ b/src/library/checkpoint/ThreadManager.h @@ -59,6 +59,7 @@ public: /* Get the thread tid */ static pid_t getThreadTid(); + /* Restore tid in all threads into their internal pthread structure */ static void restoreThreadTids(); /* Set the main thread to this thread */ @@ -95,8 +96,10 @@ public: return thread_list; } + /* Are we a child process for state saving? */ static bool isChildFork(); + /* Set the child process state */ static void setChildFork(); /* Remove a thread from the list and add it to the free list */ diff --git a/src/library/frame.cpp b/src/library/frame.cpp index 5edf2f63..2770b49b 100644 --- a/src/library/frame.cpp +++ b/src/library/frame.cpp @@ -550,19 +550,43 @@ static void receive_messages(std::function<void()> draw) AllInputs preview_ai; preview_ai.emptyInputs(); std::string savestatepath; - int index; + int slot; + + /* Catch dead children spawned for state saving */ + while (1) { + int slot = SaveStateManager::waitChild(); + if (slot < 0) break; +#ifdef LIBTAS_ENABLE_HUD + std::string msg = "State "; + msg += std::to_string(slot); + msg += " saved"; + RenderHUD::insertMessage(msg.c_str()); +#endif + } while (1) { int message = receiveMessageNonBlocking(); - /* We need to answer to ping messages from the window manager, - * otherwise the game will appear as unresponsive. */ if (message < 0) { + /* We need to answer to ping messages from the window manager, + * otherwise the game will appear as unresponsive. */ pushNativeXlibEvents(); pushNativeXcbEvents(); NATIVECALL(usleep(100)); + + /* Catch dead children spawned for state saving */ + while (1) { + int slot = SaveStateManager::waitChild(); + if (slot < 0) break; +#ifdef LIBTAS_ENABLE_HUD + std::string msg = "State "; + msg += std::to_string(slot); + msg += " saved"; + RenderHUD::insertMessage(msg.c_str()); +#endif + } } - bool succeeded; + int status; std::string str; switch (message) { @@ -618,14 +642,14 @@ static void receive_messages(std::function<void()> draw) case MSGN_SAVESTATE_INDEX: /* Get the savestate index */ - receiveData(&index, sizeof(int)); - Checkpoint::setSavestateIndex(index); + receiveData(&slot, sizeof(int)); + Checkpoint::setSavestateIndex(slot); break; case MSGN_SAVESTATE: - succeeded = SaveStateManager::checkpoint(); + status = SaveStateManager::checkpoint(slot); - if (succeeded) { + if (status == 0) { /* Current savestate is now the parent savestate */ Checkpoint::setCurrentToParent(); @@ -633,6 +657,8 @@ static void receive_messages(std::function<void()> draw) didASavestate = true; } + SaveStateManager::printError(status); + /* Don't forget that when we load a savestate, the game continues * from here and not from SaveStateManager::restore() under. */ @@ -664,9 +690,24 @@ static void receive_messages(std::function<void()> draw) /* Screen should have changed after loading */ ScreenCapture::setPixels(); } - else if (succeeded) { + else if (status == 0) { /* Tell the program that the saving succeeded */ sendMessage(MSGB_SAVING_SUCCEEDED); + + /* Print the successful message, unless we are saving in a fork */ +#ifdef LIBTAS_ENABLE_HUD + if (!(shared_config.savestate_settings & SharedConfig::SS_FORK)) { + if (shared_config.osd & SharedConfig::OSD_MESSAGES) { + std::string msg; + msg = "State "; + msg += std::to_string(slot); + msg += " saved"; + RenderHUD::insertMessage(msg.c_str()); + screen_redraw(draw, hud, preview_ai); + } + } +#endif + } else { /* A bit hackish, we must send something */ @@ -676,7 +717,9 @@ static void receive_messages(std::function<void()> draw) break; case MSGN_LOADSTATE: - SaveStateManager::restore(); + status = SaveStateManager::restore(slot); + + SaveStateManager::printError(status); /* If restoring failed, we return here. We still send the * frame count and time because the program will pull a diff --git a/src/program/GameLoop.cpp b/src/program/GameLoop.cpp index 08b08d60..65775ea2 100644 --- a/src/program/GameLoop.cpp +++ b/src/program/GameLoop.cpp @@ -949,29 +949,8 @@ bool GameLoop::processEvent(uint8_t type, struct HotKey &hk) /* Checking that saving succeeded */ int message = receiveMessage(); if (message == MSGB_SAVING_SUCCEEDED) { - if (context->config.sc.osd & SharedConfig::OSD_MESSAGES) { - std::string msg; - if (hk.type == HOTKEY_SAVESTATE_BACKTRACK) { - msg = "Backtrack state saved"; - } - else { - msg = "State "; - msg += std::to_string(statei); - msg += " saved"; - } - sendMessage(MSGN_OSD_MSG); - sendString(msg); - } - emit savestatePerformed(statei, context->framecount); } - else { - if (context->config.sc.osd & SharedConfig::OSD_MESSAGES) { - std::string msg = "State saving failed"; - sendMessage(MSGN_OSD_MSG); - sendString(msg); - } - } return false; } |