From 7a0f4e8e4cf4d42346a080421b5e9fe6cf489733 Mon Sep 17 00:00:00 2001 From: colmugx Date: Thu, 24 Sep 2026 13:05:07 +0800 Subject: [PATCH 1/4] perf: specialize the fold actor loop Give spawn_fold a dedicated serving path that handles Tell directly and treats Unit asks as FIFO receipts, removing the generic query callback and state/reply tuple from the dominant tell-oriented actor shape. Mark the command handler local for MoonBit parameter inlining. --- src/fuwaroid.mbt | 85 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/src/fuwaroid.mbt b/src/fuwaroid.mbt index 0cd542c..a64c8ab 100644 --- a/src/fuwaroid.mbt +++ b/src/fuwaroid.mbt @@ -302,13 +302,24 @@ pub fn[State, Cmd] Fuwaroid::spawn_fold( on_cmd~ : (Ctx[Cmd, Unit, Unit], State, Cmd) -> State, mailbox? : Mailbox = Mailbox::Unbounded, ) -> Fuwaroid[Cmd, Unit, Unit] { - Fuwaroid::spawn( - group~, - init~, - on_cmd~, - on_query=fold_receipt_query, - mailbox~, - ) + let kind : @aqueue.Kind = match mailbox { + Unbounded => @aqueue.Unbounded + Bounded(n) => + match mailbox_capacity_error(n) { + Some(message) => + abort("fuwaroid: invalid mailbox configuration: \{message}") + None => @aqueue.Blocking(n) + } + } + let queue : @aqueue.Queue[Envelope[Cmd, Unit, Unit]] = @aqueue.Queue(kind~) + let stop = LoopStop::fresh() + let fuwaroid : Fuwaroid[Cmd, Unit, Unit] = { mailbox: queue, stop, } + let ctx : Ctx[Cmd, Unit, Unit] = { group, address: fuwaroid, } + let task = group.spawn(no_wait=true, () => { + fuwaroid_loop_fold(ctx, init, on_cmd, queue, stop~) + }) + stop.task = Some(task) + fuwaroid } ///| @@ -349,6 +360,66 @@ fn[Cmd, Query, Reply] abandon_envelope( } } +///| +async fn[State, Cmd] fuwaroid_loop_fold( + ctx : Ctx[Cmd, Unit, Unit], + init : State, + on_cmd : (Ctx[Cmd, Unit, Unit], State, Cmd) -> State, + queue : @aqueue.Queue[Envelope[Cmd, Unit, Unit]], + stop~ : LoopStop, +) -> Unit { + match @async.handle_cancellation(() => { + fuwaroid_serve_fold(ctx, init, on_cmd, queue, stop~) + }) { + Some(_) => () + None => { + fuwaroid_cleanup(queue, Cancelled, stop) + @async.pause() + } + } +} + +///| +#locals(on_cmd) +async fn[State, Cmd] fuwaroid_serve_fold( + ctx : Ctx[Cmd, Unit, Unit], + init : State, + on_cmd : (Ctx[Cmd, Unit, Unit], State, Cmd) -> State, + queue : @aqueue.Queue[Envelope[Cmd, Unit, Unit]], + stop~ : LoopStop, +) -> Unit { + let reason : StopReason = try { + for envelope = queue.get(), + state = init, + served = 0 { + let state = match envelope { + Tell(cmd) => on_cmd(ctx, state, cmd) + Ask(_, reply) => { + let _ = reply.try_put(()) catch { _ => false } + state + } + } + let served = served + 1 + stop.diag.processed += 1L + + if served >= fuwaroid_yield_batch { + @async.pause() + continue queue.get(), state, 0 + } + + match queue.try_get() { + Some(next) => continue next, state, served + None => continue queue.get(), state, served + } + } + Graceful + } catch { + @aqueue.QueueAlreadyClosed => Graceful + original => Failed(original) + } + fuwaroid_cleanup(queue, reason, stop) +} + ///| /// Cancellation is handled separately from ordinary loop errors. async fn[State, Cmd, Query, Reply] fuwaroid_loop( From 47e81ab08b2d9906bcddc086aed9d14e8fcae606 Mon Sep 17 00:00:00 2001 From: colmugx Date: Thu, 24 Sep 2026 13:05:17 +0800 Subject: [PATCH 2/4] ci: benchmark specialized spawn_fold experiment --- .github/workflows/concurrency-benchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/concurrency-benchmark.yml b/.github/workflows/concurrency-benchmark.yml index 73edf25..371d594 100644 --- a/.github/workflows/concurrency-benchmark.yml +++ b/.github/workflows/concurrency-benchmark.yml @@ -9,7 +9,7 @@ on: required: true type: string push: - branches: [main] + branches: [main, perf/specialize-spawn-fold] permissions: contents: read From 69f48a8b8b86c0f2bf414c5db8a9f7bfdbfeaaff Mon Sep 17 00:00:00 2001 From: colmugx Date: Thu, 24 Sep 2026 13:08:36 +0800 Subject: [PATCH 3/4] refactor: remove obsolete fold query shim --- src/fuwaroid.mbt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/fuwaroid.mbt b/src/fuwaroid.mbt index a64c8ab..6e2cb99 100644 --- a/src/fuwaroid.mbt +++ b/src/fuwaroid.mbt @@ -207,17 +207,6 @@ fn LoopStop::fresh() -> LoopStop { { reason: None, cleanup_error: None, task: None, diag: Diag::fresh(), } } -///| -/// No-op query handler for `spawn_fold`. -#inline -fn[State, Cmd] fold_receipt_query( - _ctx : Ctx[Cmd, Unit, Unit], - state : State, - _query : Unit, -) -> (State, Unit) { - (state, ()) -} - ///| /// Shared admission path for `tell` and `ask`. #inline From 7a7784adf7c30822be3b3077e113dfc4f85f8416 Mon Sep 17 00:00:00 2001 From: colmugx Date: Thu, 24 Sep 2026 13:08:39 +0800 Subject: [PATCH 4/4] ci: remove temporary spawn_fold benchmark trigger --- .github/workflows/concurrency-benchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/concurrency-benchmark.yml b/.github/workflows/concurrency-benchmark.yml index 371d594..73edf25 100644 --- a/.github/workflows/concurrency-benchmark.yml +++ b/.github/workflows/concurrency-benchmark.yml @@ -9,7 +9,7 @@ on: required: true type: string push: - branches: [main, perf/specialize-spawn-fold] + branches: [main] permissions: contents: read