host: per-request IO kernel fix + fully-dynamic blog (no cache), 159/159
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 16s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 16s
KERNEL (sx_server.ml): route http-listen handlers through cek_run_with_io instead of bare Sx_runtime.sx_call, so handlers resolve per-request IO (durable persist reads/writes) via the same IO-driving runner the REPL uses. Verified: per-request read+write, 10 concurrent writes (15 on disk, no corruption), handler errors don't crash the server, http contract 6/6. BLOG: fully dynamic — host/blog-post reads the post from the durable store (content/head) AND renders (content/html) per request, no in-memory view, no cached output. Possible because of the IO fix. Honest ~2s due to interpreted Smalltalk render. Render speed is NOT solved here: the JIT (precompiler) isn't installed in the serving mode and currently miscompiles the Smalltalk evaluator's nested ASTs (enabling it breaks ~60% of tests). Fixing the JIT is a separate, high-payoff effort. Documented in the plan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
;; lib/host/blog.sx — Blog domain on the host. Posts are content-on-sx documents
|
||||
;; whose SOURCE OF TRUTH is the durable SX store (persist op-log on disk): a post
|
||||
;; is published by appending insert ops to its stream. Serving GET /<slug>/ renders
|
||||
;; the post to HTML via content/html. The original strangler target (Quart blog
|
||||
;; whose source of truth is the durable SX store (persist op-log on disk). Serving
|
||||
;; GET /<slug>/ is FULLY DYNAMIC: the handler reads the post from the store and
|
||||
;; renders it to HTML, per request — no in-memory view, no cached output. This is
|
||||
;; possible because http-listen handlers now resolve per-request IO (the
|
||||
;; cek_run_with_io kernel fix). The original strangler target (Quart blog
|
||||
;; post_detail); published posts are world-visible, so this endpoint is ANONYMOUS.
|
||||
;;
|
||||
;; READ PATH — materialised view, not per-request IO. The durable backend reads
|
||||
;; via `perform` (kernel IO suspension), which is serviceable on the main thread
|
||||
;; (boot) but NOT inside an http-listen request handler thread. So posts are
|
||||
;; materialised from the store into an in-memory view at boot (and on publish),
|
||||
;; and request handlers read that view — fast, perform-free. The store stays the
|
||||
;; source of truth; the view is a cache rebuilt from it on startup.
|
||||
;; NOTE ON SPEED: content/html runs the interpreted Smalltalk-on-SX dispatch
|
||||
;; (~2s for a tiny doc) because the JIT is not installed in this serving mode AND
|
||||
;; currently miscompiles the Smalltalk evaluator's nested ASTs. Making the render
|
||||
;; fast is a JIT-compiler fix (or a Smalltalk-interpreter optimisation), tracked
|
||||
;; separately — it is NOT solved by caching the output.
|
||||
;; Depends on lib/content/* (+ Smalltalk + persist preloads) + lib/dream/* +
|
||||
;; lib/host/handler.sx.
|
||||
|
||||
@@ -17,20 +18,13 @@
|
||||
(define host/blog-bootstrap!
|
||||
(fn () (begin (st-bootstrap-classes!) (content/bootstrap!))))
|
||||
|
||||
;; ── store (durable source of truth) + view (in-memory serving cache) ─
|
||||
;; ── store (durable source of truth, injectable) ─────────────────────
|
||||
(define host/blog-store (persist/open))
|
||||
(define host/blog-view {})
|
||||
(define host/blog-use-store!
|
||||
(fn (b) (begin (set! host/blog-store b) (set! host/blog-view {}))))
|
||||
(define host/blog-use-store! (fn (b) (set! host/blog-store b)))
|
||||
|
||||
;; content streams are keyed "content:<slug>"; recover the slug.
|
||||
(define host/blog--stream-slug
|
||||
(fn (stream)
|
||||
(if (starts-with? stream "content:") (substr stream 8) nil)))
|
||||
|
||||
;; ── publish + lookup ────────────────────────────────────────────────
|
||||
;; ── publish + lookup (per-request, against the store) ───────────────
|
||||
;; Publish a simple post (title heading + body paragraph): append its insert ops
|
||||
;; to the durable store, then refresh the in-memory view. `at` is a logical ts.
|
||||
;; to the durable store. `at` is a caller-supplied logical timestamp.
|
||||
(define host/blog-publish!
|
||||
(fn (slug title body at)
|
||||
(let ((hid (str slug "-h")) (tid (str slug "-body")))
|
||||
@@ -38,35 +32,21 @@
|
||||
(list
|
||||
(op-insert (mk-heading hid 1 title) nil)
|
||||
(op-insert (mk-text tid body) hid))
|
||||
at)
|
||||
(set! host/blog-view
|
||||
(assoc host/blog-view slug (content/head host/blog-store slug))))))
|
||||
at))))
|
||||
|
||||
;; Materialise every persisted post from the store into the view. Run at boot on
|
||||
;; the main thread (content/head performs IO, fine here, not in a request).
|
||||
(define host/blog-load-all!
|
||||
(fn ()
|
||||
(for-each
|
||||
(fn (stream)
|
||||
(let ((slug (host/blog--stream-slug stream)))
|
||||
(when slug
|
||||
(let ((doc (content/head host/blog-store slug)))
|
||||
(when (> (content/count doc) 0)
|
||||
(set! host/blog-view (assoc host/blog-view slug doc)))))))
|
||||
(persist/backend-streams host/blog-store))))
|
||||
|
||||
;; Idempotent seed: if the slug isn't already materialised, recover it from the
|
||||
;; store (prior run) or publish it fresh. No duplicate ops on restart.
|
||||
;; Idempotent seed: publish only if the slug has no content yet (so a restart
|
||||
;; replaying serve.sh doesn't append duplicate blocks to a persisted post).
|
||||
(define host/blog-seed!
|
||||
(fn (slug title body at)
|
||||
(when (nil? (get host/blog-view slug))
|
||||
(let ((existing (content/head host/blog-store slug)))
|
||||
(if (> (content/count existing) 0)
|
||||
(set! host/blog-view (assoc host/blog-view slug existing))
|
||||
(host/blog-publish! slug title body at))))))
|
||||
(when (= (content/count (content/head host/blog-store slug)) 0)
|
||||
(host/blog-publish! slug title body at))))
|
||||
|
||||
;; Lookup is pure in-memory (no perform) — safe inside a request handler.
|
||||
(define host/blog-lookup (fn (slug) (get host/blog-view slug)))
|
||||
;; Materialise the post from the store by replaying its op-log; nil if no content.
|
||||
;; Reads the durable store via per-request IO (works inside the handler thread).
|
||||
(define host/blog-lookup
|
||||
(fn (slug)
|
||||
(let ((doc (content/head host/blog-store slug)))
|
||||
(if (> (content/count doc) 0) doc nil))))
|
||||
|
||||
;; ── handler: GET /<slug>/ -> rendered HTML (200) or 404 ─────────────
|
||||
(define host/blog-post
|
||||
|
||||
Reference in New Issue
Block a user