Skip to content

Commit

Permalink
ConfigFile: Implement async write
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Zirias committed Oct 8, 2024
1 parent b93f12e commit 73ba1d4
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 24 deletions.
6 changes: 3 additions & 3 deletions src/bin/xmoji/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
130 changes: 110 additions & 20 deletions src/bin/xmoji/configfile.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,23 @@ 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;
FileWatcher *watcher;
PSC_Event *changed;
const char **keys;
ReadContext *readContext;
WriteContext *writeContext;
size_t nkeys;
int dirty;
char *vals[];
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion src/bin/xmoji/configfile.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit 73ba1d4

Please sign in to comment.