aboutsummaryrefslogtreecommitdiffstats
path: root/src/server.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-14 19:32:02 +0200
committeruser <user@clank>2026-08-14 19:32:02 +0200
commit6a6799bfc0eb8662df12b636e4fe537b84a5d484 (patch)
tree887b96ef94def60fad351cf41070a86be94a7f56 /src/server.c
parentsl0ppty: saving the config reloads it (diff)
downloadslosh-6a6799bfc0eb8662df12b636e4fe537b84a5d484.tar.gz
slosh-6a6799bfc0eb8662df12b636e4fe537b84a5d484.tar.xz
slosh-6a6799bfc0eb8662df12b636e4fe537b84a5d484.zip
sl0ppty: one save, one reload
A double "config reloaded" was reported, and the immediate cause was me saving the file twice in a row — but looking properly turned up a real one underneath. One save is not always one inotify event. Watching a directory for CLOSE_WRITE, MOVED_TO and CREATE, an editor that *creates* the file rather than rewriting it produces CREATE and then CLOSE_WRITE, measured, and each one was reloading the config and announcing it. Rewriting in place gives one event, and so does the rename-over that sed and vim do, which is why it took a specific kind of editor to see it. Events now arm a deadline rather than reloading, and the reload happens once the file has been quiet for eighty milliseconds. That collapses the double, and also means an editor that writes in chunks is read after it has finished rather than halfway through — which should make the mid-save parse error, the thing the fail-open behaviour exists for, rarer than it was. The test counts the toasts, because toasts stack: a config that reloaded twice says so twice, which is exactly what was seen on screen. I checked it against the un-debounced build first and it reported two, so it is testing the thing it claims to.
Diffstat (limited to 'src/server.c')
-rw-r--r--src/server.c37
1 files changed, 26 insertions, 11 deletions
diff --git a/src/server.c b/src/server.c
index 6baf52d..d2c704a 100644
--- a/src/server.c
+++ b/src/server.c
@@ -211,6 +211,13 @@ int server_run(const char *name, const char *const argv[], uint16_t cols,
* then never again, which is worse than not watching at all because it looks
* like it works. Watching the directory and filtering by name survives that,
* and picks up a file that did not exist when the session started. */
+ /* One save is not always one event: an editor that creates the file rather
+ * than rewriting it produces CREATE and then CLOSE_WRITE, and reloading on
+ * each would parse the config twice and announce it twice for one edit.
+ * Waiting a moment for the file to stop changing also means an editor that
+ * writes in chunks is read once it has finished rather than halfway. */
+ const int64_t RELOAD_DEBOUNCE_MS = 80;
+ int64_t reload_due = -1;
int inofd = -1, inowd = -1;
char cfg_dir[512] = {0}, cfg_base[256] = {0};
if (watch) {
@@ -268,6 +275,11 @@ int server_run(const char *name, const char *const argv[], uint16_t cols,
}
/* some things happen without an event: a toast expiring, a hover guide
* arming under a pointer that is deliberately not moving */
+ if (reload_due >= 0) {
+ int64_t due = reload_due - now_ms();
+ int t = due <= 0 ? 0 : (int)due;
+ if (timeout < 0 || t < timeout) timeout = t;
+ }
int self_due = app_next_deadline_ms(s.app);
if (self_due >= 0 && (timeout < 0 || self_due < timeout)) {
timeout = self_due;
@@ -394,18 +406,21 @@ int server_run(const char *name, const char *const argv[], uint16_t cols,
q += sizeof *ev + ev->len;
}
}
- if (touched) {
- char err[256] = {0};
- if (app_reload_config(err, sizeof err)) {
- app_resize(s.app, s.screen.cols, s.screen.rows);
- app_toast(s.app, "config reloaded");
- } else {
- app_toast(s.app, err[0] ? err : "config reload failed");
- }
- s.screen.force_full = true;
- pending_paint = true;
- next_frame = now_ms();
+ if (touched) reload_due = now_ms() + RELOAD_DEBOUNCE_MS;
+ }
+
+ if (reload_due >= 0 && now_ms() >= reload_due) {
+ reload_due = -1;
+ char err[256] = {0};
+ if (app_reload_config(err, sizeof err)) {
+ app_resize(s.app, s.screen.cols, s.screen.rows);
+ app_toast(s.app, "config reloaded");
+ } else {
+ app_toast(s.app, err[0] ? err : "config reload failed");
}
+ s.screen.force_full = true;
+ pending_paint = true;
+ next_frame = now_ms();
}
app_reap(s.app);