summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClément Gallet <clement.gallet@ens-lyon.org>2020-07-10 13:29:56 +0200
committerClément Gallet <clement.gallet@ens-lyon.org>2020-07-10 13:29:56 +0200
commitfa4e69dbcbf6db26be6f60fcb6a3bcd1fdc8f3f1 (patch)
tree64116520e1c12230503341a2c465a4ba15ef51ab
parente96250eac2fa1c65ee4f9cce28b52e7ca9f760ae (diff)
Check for forked savestates to completefork
-rw-r--r--src/library/checkpoint/Checkpoint.cpp29
-rw-r--r--src/library/checkpoint/Checkpoint.h4
-rw-r--r--src/library/checkpoint/ReservedMemory.h6
-rw-r--r--src/library/checkpoint/SaveStateManager.cpp94
-rw-r--r--src/library/checkpoint/SaveStateManager.h40
-rw-r--r--src/library/checkpoint/ThreadManager.cpp1
-rw-r--r--src/library/checkpoint/ThreadManager.h3
-rw-r--r--src/library/frame.cpp63
-rw-r--r--src/program/GameLoop.cpp21
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;
}