From 73ba1d414202c0a5e3bc8899383131131620b35a Mon Sep 17 00:00:00 2001 From: Felix Palmen Date: Tue, 8 Oct 2024 15:06:15 +0200 Subject: [PATCH] ConfigFile: Implement async write New 'sync' parameter for ConfigFile_write(), if zero and the thread pool is available, do the write operations in a thread job to avoid stalling the main thread on I/O issues. --- src/bin/xmoji/config.c | 6 +- src/bin/xmoji/configfile.c | 130 +++++++++++++++++++++++++++++++------ src/bin/xmoji/configfile.h | 2 +- 3 files changed, 114 insertions(+), 24 deletions(-) diff --git a/src/bin/xmoji/config.c b/src/bin/xmoji/config.c index ce257bf..e4e9393 100644 --- a/src/bin/xmoji/config.c +++ b/src/bin/xmoji/config.c @@ -99,7 +99,7 @@ static void writeNum(Config *self, enum ConfigKey key, long val) char buf[32]; snprintf(buf, 32, "%ld", val); ConfigFile_set(self->cfg, keys[key], PSC_copystr(buf)); - ConfigFile_write(self->cfg); + ConfigFile_write(self->cfg, 0); } static void readSingleInstance(Config *self) @@ -255,7 +255,7 @@ static void historychanged(void *receiver, void *sender, void *args) if (self->reading) return; ConfigFile_set(self->cfg, keys[CFG_HISTORY], EmojiHistory_serialize(self->history)); - ConfigFile_write(self->cfg); + ConfigFile_write(self->cfg, 0); } static char *canonicalpath(const char *path) @@ -384,7 +384,7 @@ Config *Config_create(const char *path) readers[i](self); } self->reading = 0; - if (ConfigFile_write(self->cfg) >= 0) + if (ConfigFile_write(self->cfg, 1) >= 0) { PSC_Event_register(ConfigFile_changed(self->cfg), self, filechanged, 0); diff --git a/src/bin/xmoji/configfile.c b/src/bin/xmoji/configfile.c index f9c3df0..c2ccc1c 100644 --- a/src/bin/xmoji/configfile.c +++ b/src/bin/xmoji/configfile.c @@ -23,6 +23,15 @@ typedef struct ReadContext char *vals[]; } ReadContext; +typedef struct WriteContext +{ + ConfigFile *configFile; + char *tmppath; + PSC_ThreadJob *job; + int rc; + char *vals[]; +} WriteContext; + struct ConfigFile { const char *path; @@ -30,6 +39,7 @@ struct ConfigFile PSC_Event *changed; const char **keys; ReadContext *readContext; + WriteContext *writeContext; size_t nkeys; int dirty; char *vals[]; @@ -216,6 +226,7 @@ ConfigFile *ConfigFile_create(const char *path, size_t nkeys, self->changed = PSC_Event_create(self); self->keys = keys; self->readContext = 0; + self->writeContext = 0; self->nkeys = nkeys; memset(self->vals, 0, nkeys * sizeof *self->vals); PSC_Event_register(FileWatcher_changed(self->watcher), self, @@ -265,34 +276,113 @@ static int ensurepath(char *current) return rc; } -int ConfigFile_write(ConfigFile *self) +static void movejobfinished(void *receiver, void *sender, void *args) { - int rc = -1; - size_t nmlen = strlen(self->path); - char *tmpnm = PSC_malloc(nmlen + sizeof TMPSUFX); - memcpy(tmpnm, self->path, nmlen); - memcpy(tmpnm+nmlen, TMPSUFX, sizeof TMPSUFX); - if (ensurepath(tmpnm) < 0) goto done; - FILE *f = fopen(tmpnm, "w"); - if (!f) goto done; + (void)sender; + + ConfigFile *self = receiver; + WriteContext *ctx = args; + + FileWatcher_watch(self->watcher); + if (self->writeContext == ctx) self->writeContext = 0; + free(ctx->tmppath); for (size_t i = 0; i < self->nkeys; ++i) { - if (!self->vals[i]) continue; - if (fprintf(f, "%s = %s\n", self->keys[i], self->vals[i]) < 1) + free(ctx->vals[i]); + } + free(ctx); +} + +static void movejob(void *arg) +{ + WriteContext *ctx = arg; + if (rename(ctx->tmppath, ctx->configFile->path) >= 0) ctx->rc = 0; +} + +static void writejobfinished(void *receiver, void *sender, void *args) +{ + ConfigFile *self = receiver; + PSC_ThreadJob *job = sender; + WriteContext *ctx = args; + + if (ctx->rc == -1 && (!job || PSC_ThreadJob_hasCompleted(job))) + { + FileWatcher_unwatch(self->watcher); + if (job) { - fclose(f); - unlink(tmpnm); - goto done; + ctx->job = PSC_ThreadJob_create(movejob, ctx, 0); + PSC_Event_register(PSC_ThreadJob_finished(ctx->job), + self, movejobfinished, 0); + PSC_ThreadPool_enqueue(ctx->job); } + else movejob(ctx); + return; } - fclose(f); - FileWatcher_unwatch(self->watcher); - if (rename(tmpnm, self->path) < 0) goto done; - rc = 0; - FileWatcher_watch(self->watcher); + + if (job) movejobfinished(self, job, ctx); +} + +static void writejob(void *arg) +{ + WriteContext *ctx = arg; + FILE *f = 0; + if (ensurepath(ctx->tmppath) < 0) goto done; + if (ctx->job && PSC_ThreadJob_canceled()) goto done; + f = fopen(ctx->tmppath, "w"); + if (!f || (ctx->job && PSC_ThreadJob_canceled())) goto done; + for (size_t i = 0; i < ctx->configFile->nkeys; ++i) + { + if (!ctx->vals[i]) continue; + if (fprintf(f, "%s = %s\n", + ctx->configFile->keys[i], ctx->vals[i]) < 1) goto done; + if (ctx->job && PSC_ThreadJob_canceled()) goto done; + } + ctx->rc = -1; done: - free(tmpnm); + if (f) + { + fclose(f); + if (ctx->rc < -1) unlink(ctx->tmppath); + } + if (!ctx->job) writejobfinished(ctx->configFile, 0, ctx); +} + +int ConfigFile_write(ConfigFile *self, int sync) +{ + if (self->writeContext) + { + PSC_ThreadPool_cancel(self->writeContext->job); + } + + self->writeContext = PSC_malloc(sizeof *self->writeContext + + self->nkeys * sizeof *self->writeContext->vals); + self->writeContext->configFile = self; + size_t nmlen = strlen(self->path); + self->writeContext->tmppath = PSC_malloc(nmlen + sizeof TMPSUFX); + memcpy(self->writeContext->tmppath, self->path, nmlen); + memcpy(self->writeContext->tmppath+nmlen, TMPSUFX, sizeof TMPSUFX); + self->writeContext->rc = -2; + for (size_t i = 0; i < self->nkeys; ++i) + { + self->writeContext->vals[i] = self->vals[i] + ? PSC_copystr(self->vals[i]) : 0; + } + + if (!sync && PSC_ThreadPool_active()) + { + self->writeContext->job = PSC_ThreadJob_create(writejob, + self->writeContext, 0); + PSC_Event_register(PSC_ThreadJob_finished(self->writeContext->job), + self, writejobfinished, 0); + PSC_ThreadPool_enqueue(self->writeContext->job); + return 0; + } + + self->writeContext->job = 0; + writejob(self->writeContext); + int rc = self->writeContext->rc; + movejobfinished(self, 0, self->writeContext); return rc; } diff --git a/src/bin/xmoji/configfile.h b/src/bin/xmoji/configfile.h index 4a8dd31..aa681a5 100644 --- a/src/bin/xmoji/configfile.h +++ b/src/bin/xmoji/configfile.h @@ -20,7 +20,7 @@ const char *ConfigFile_get(const ConfigFile *self, const char *key) CMETHOD ATTR_NONNULL((2)); PSC_Event *ConfigFile_changed(ConfigFile *self) CMETHOD; -int ConfigFile_write(ConfigFile *self) +int ConfigFile_write(ConfigFile *self, int sync) CMETHOD; void ConfigFile_destroy(ConfigFile *self);