Skip to content

Commit 6f0e6b9

Browse files
committed
ni
1 parent cda68ed commit 6f0e6b9

2 files changed

Lines changed: 76 additions & 11 deletions

File tree

src/picomesh/plugins/relational_storage/relational_storage.c

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,21 @@
6262
* file, not in this plugin. A node that provides no `schema` gets empty
6363
* SQLite shards and runs its own DDL through `exec`. */
6464

65+
/* Per-shard prepared-statement cache entry. Statements live the process
66+
* lifetime (like the shard), so re-preparing the same SQL — which re-runs the
67+
* SQLite parser + query planner on every request, a measured hot path — is
68+
* avoided: each distinct query is parsed once, then reset+rebound per call. */
69+
struct rel_stmt {
70+
char *sql;
71+
sqlite3_stmt *stmt;
72+
};
73+
6574
struct rel_shard {
6675
sqlite3 *db;
6776
pthread_mutex_t mu;
77+
struct rel_stmt *cache; /* prepared-statement cache, guarded by `mu` */
78+
int cache_n;
79+
int cache_cap;
6880
};
6981

7082
/* One NAMED logical database == one shard set. A single relational_storage
@@ -390,8 +402,42 @@ struct rel_work {
390402
char err[256];
391403
};
392404

393-
/* Runs on a worker-pool thread: prepare + bind + step on the shard_key's shard,
394-
* serialized by the shard mutex; build the JSON result. */
405+
/* Return a prepared, reset+cleared statement for `sql` from the shard's cache,
406+
* preparing (and caching) it on first sight. Caller MUST hold sh->mu. Returns
407+
* NULL and sets *errmsg on failure. The statement is owned by the cache (never
408+
* finalized by the caller — reset it after use); the cache + statements live
409+
* the process lifetime, matching the shard. */
410+
static sqlite3_stmt *rel_shard_stmt(struct rel_shard *sh, const char *sql, const char **errmsg)
411+
{
412+
for (int i = 0; i < sh->cache_n; ++i) {
413+
if (strcmp(sh->cache[i].sql, sql) == 0) {
414+
sqlite3_reset(sh->cache[i].stmt);
415+
sqlite3_clear_bindings(sh->cache[i].stmt);
416+
return sh->cache[i].stmt;
417+
}
418+
}
419+
sqlite3_stmt *st = NULL;
420+
if (sqlite3_prepare_v2(sh->db, sql, -1, &st, NULL) != SQLITE_OK) {
421+
if (errmsg) *errmsg = sqlite3_errmsg(sh->db);
422+
return NULL;
423+
}
424+
if (sh->cache_n == sh->cache_cap) {
425+
int ncap = sh->cache_cap ? sh->cache_cap * 2 : 16;
426+
struct rel_stmt *nc = realloc(sh->cache, (size_t)ncap * sizeof(*nc));
427+
if (!nc) { sqlite3_finalize(st); if (errmsg) *errmsg = "stmt cache: out of memory"; return NULL; }
428+
sh->cache = nc;
429+
sh->cache_cap = ncap;
430+
}
431+
char *sql_copy = strdup(sql);
432+
if (!sql_copy) { sqlite3_finalize(st); if (errmsg) *errmsg = "stmt cache: out of memory"; return NULL; }
433+
sh->cache[sh->cache_n].sql = sql_copy;
434+
sh->cache[sh->cache_n].stmt = st;
435+
sh->cache_n++;
436+
return st;
437+
}
438+
439+
/* Runs on a worker-pool thread: bind + step a (cached) prepared statement on
440+
* the shard_key's shard, serialized by the shard mutex; build the JSON result. */
395441
static void rel_work_fn(void *arg)
396442
{
397443
struct rel_work *w = arg;
@@ -402,9 +448,12 @@ static void rel_work_fn(void *arg)
402448
struct rel_shard *sh = &s->shards[(uint64_t)w->shard_key % (uint64_t)s->n];
403449

404450
pthread_mutex_lock(&sh->mu);
405-
sqlite3_stmt *st = NULL;
406-
if (sqlite3_prepare_v2(sh->db, w->sql, -1, &st, NULL) != SQLITE_OK) {
407-
snprintf(w->err, sizeof(w->err), "prepare: %s", sqlite3_errmsg(sh->db));
451+
/* Cached prepared statement (parsed once per distinct SQL, then reset+
452+
* rebound) — avoids re-running the SQLite parser/planner on every call. */
453+
const char *prep_err = NULL;
454+
sqlite3_stmt *st = rel_shard_stmt(sh, w->sql, &prep_err);
455+
if (!st) {
456+
snprintf(w->err, sizeof(w->err), "prepare: %s", prep_err ? prep_err : "?");
408457
pthread_mutex_unlock(&sh->mu);
409458
return;
410459
}
@@ -416,13 +465,13 @@ static void rel_work_fn(void *arg)
416465
if (want != w->nbinds) {
417466
snprintf(w->err, sizeof(w->err),
418467
"bind count mismatch: SQL has %d parameter(s), got %d", want, w->nbinds);
419-
sqlite3_finalize(st);
468+
sqlite3_reset(st);
420469
pthread_mutex_unlock(&sh->mu);
421470
return;
422471
}
423472
if (rel_bind_args(st, w->binds, w->nbinds) != SQLITE_OK) {
424473
snprintf(w->err, sizeof(w->err), "bind: %s", sqlite3_errmsg(sh->db));
425-
sqlite3_finalize(st);
474+
sqlite3_reset(st);
426475
pthread_mutex_unlock(&sh->mu);
427476
return;
428477
}
@@ -441,7 +490,7 @@ static void rel_work_fn(void *arg)
441490

442491
if (w->op == REL_QUERY) {
443492
jw = yjson_writer_new();
444-
if (!jw) { sqlite3_finalize(st); pthread_mutex_unlock(&sh->mu);
493+
if (!jw) { sqlite3_reset(st); pthread_mutex_unlock(&sh->mu);
445494
snprintf(w->err, sizeof(w->err), "writer alloc"); return; }
446495
yjson_writer_begin_array(jw);
447496
int ncol = sqlite3_column_count(st);
@@ -457,21 +506,21 @@ static void rel_work_fn(void *arg)
457506
yjson_writer_end_array(jw);
458507
if (rc != SQLITE_DONE) {
459508
snprintf(w->err, sizeof(w->err), "step: %s", sqlite3_errmsg(sh->db));
460-
yjson_writer_free(jw); sqlite3_finalize(st); pthread_mutex_unlock(&sh->mu);
509+
yjson_writer_free(jw); sqlite3_reset(st); pthread_mutex_unlock(&sh->mu);
461510
return;
462511
}
463512
} else { /* REL_EXEC */
464513
int rc = sqlite3_step(st);
465514
if (rc != SQLITE_DONE && rc != SQLITE_ROW) {
466515
snprintf(w->err, sizeof(w->err), "exec: %s", sqlite3_errmsg(sh->db));
467-
sqlite3_finalize(st); pthread_mutex_unlock(&sh->mu);
516+
sqlite3_reset(st); pthread_mutex_unlock(&sh->mu);
468517
return;
469518
}
470519
exec_changes = sqlite3_changes(sh->db);
471520
exec_rowid = sqlite3_last_insert_rowid(sh->db);
472521
}
473522

474-
sqlite3_finalize(st);
523+
sqlite3_reset(st);
475524
pthread_mutex_unlock(&sh->mu);
476525
/* --- DB lock released: serialize + allocate the result OUTSIDE it. --- */
477526

tools/picoforge/yemu/web/build.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,22 @@ cp -vL "$YEMU_BUILD/kernel-riscv64.bin" "$OUT/assets/"
7474
cp -vL "$LOCAL_OPENSBI" "$OUT/assets/opensbi-fw_jump.elf"
7575
cp -vL "$YEMU_BUILD/alpine-rootfs.img" "$OUT/assets/"
7676

77+
# Stage the webapp static tree (CSS + the Monaco / EasyMDE rich-text editor
78+
# vendor) under $OUT/static. The in-browser pages render inside a `srcdoc`
79+
# iframe whose base origin is THIS dev server (serve.py / GitHub Pages), so
80+
# Monaco's runtime AMD require()/XHR for /static/vendor/monaco/vs/* resolves
81+
# HERE — not through the slirp bridge (which only inlines the initial <script>
82+
# tags, never Monaco's dynamically-loaded modules + workers). Without this the
83+
# rich-text editor can't load and the editor area is blank.
84+
STATIC_SRC="$REPO_ROOT/assets/picoforge/static"
85+
if [ -d "$STATIC_SRC" ]; then
86+
echo "==> staging webapp static tree (Monaco/EasyMDE) under $OUT/static/"
87+
rm -rf "$OUT/static"
88+
cp -a "$STATIC_SRC" "$OUT/static"
89+
else
90+
echo "WARN: $STATIC_SRC missing — the in-browser rich-text editor will not load" >&2
91+
fi
92+
7793
echo
7894
echo "Build ready under $OUT/:"
7995
ls -lh "$OUT/picomesh-yemu.js" "$OUT/picomesh-yemu.wasm" "$OUT/assets/"

0 commit comments

Comments
 (0)