-
Notifications
You must be signed in to change notification settings - Fork 0
Add streaming response support: client, server, and concurrent handlers #641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
132 commits
Select commit
Hold shift + click to select a range
4ff24cf
feat: outbound HTTP response streaming (stream response / wait for ne…
claude 6991f58
docs: lock design for streamed server responses (item 3) and streamin…
claude 73e66dd
feat: streamed server responses (start streaming response / write lin…
claude ed61ba5
feat: concurrent request handlers (main loop concurrently:)
claude e704b87
fix: address PR #641 review — backward-compat, status validation, doc…
claude ed89b65
ci: skip non-executable streaming docs examples in run_integration_tests
claude d2d88c4
docs: adopt Logbie Testing Policy (root testing.md) + WFL testing pro…
claude ad6c099
fix: streaming bindings use define_or_replace (loop re-reads + no bin…
claude 465c3da
fix: address PR #641 review — transpile-fail, streaming type checks, …
claude 149d1c6
fix: isolate per-handler run-state under `main loop concurrently:` (P…
claude c45c276
fix: auto-close server response streams on handler exit (spec item 5;…
claude 04f7983
fix: 500 immediately when a handler ends without responding (P1 #3)
claude c20bec5
fix: `write line/chunk <var> to <file>` keeps the classic file write …
claude c243056
docs: reconcile concurrency/testing status with shipped reality (review)
claude 5f1d1bc
fix: parse `content type <var>` in start-streaming; tighten review te…
claude 143621b
docs: qualify concurrent-handler isolation wording in example (review)
claude 4448b19
test: wait for server readiness instead of a fixed sleep (fix flaky CI)
claude 6f14943
fix: mark streaming-statement vars used in analyzer; tidy body type h…
claude 19ebb79
test+docs: broaden streaming unused-var test; qualify phase-plan clai…
claude 90137fc
docs: align write-chunk value types, note write-line `with` limit, dr…
claude 1e48764
fix: drop RefCell borrows before await; re-enable await_holding_refce…
claude ae518ce
fix: budget-check server write size before materializing bytes (P1 #6)
claude 3366021
fix: finalize top-level streams/requests on every exit path (P1 #5)
claude d32f3fd
fix: only dispatch `start` to streaming when `streaming` follows (bac…
claude b632b59
fix: back off and cap consecutive failures in concurrent main loop (P…
claude f74bd37
feat: absolute total deadline for outbound streaming responses (P1 #2)
claude 1778efb
ci: gate docs-validation and web tests; make scripts executable (P1 g…
claude 7e0ea7a
test: surface server-thread interpret errors and re-raise join panics
claude 430db00
fix: tighten ambiguous write line/chunk analysis
claude 6127e72
fix: require wait-for-request first in concurrent loop; enforce text …
claude d86720a
docs: restore report_undefined_name doc comment to its function
claude 6ea80e2
fix(ci): give Start-Process distinct stdout/stderr targets in web tes…
claude 59d4809
fix(ci): address WFL server on 127.0.0.1 (not localhost) in Windows w…
claude a7c3a23
fix(ci): deterministic flush + monotonic deadline + exit code in web …
claude 4a678bd
fix: robust redirect Location extraction; exact streaming dispatch; h…
claude 9ea03c0
fix: parse with-continuation in write line/chunk; harden Windows web …
claude 12dc3ea
docs: write line/chunk now supports with-concatenation directly
claude 7b26602
fix: parse ambiguous write line/chunk readings independently; analyze…
claude 6e8c7eb
fix(typechecker): accept stream handles in close, not just File
claude 5259a1d
fix(typechecker): only File custom type is closeable; drop misleading…
claude 982fe7b
fix(ci): guard Kill() and warn before racing TLS temp cleanup
claude 31cda41
fix(typechecker): distinct stream-handle types so close rejects ordin…
claude 38e6bde
style: rustfmt the stream-handle index-access match arm
claude 2cc153f
fix(analyzer): stop rejecting valid classic writes with desugared values
claude 8852786
fix(typechecker): support dot access on stream handles; require text …
claude 8a8df6a
fix(ci): surface TLS temp-dir cleanup failure instead of hiding it
claude c912f57
fix(parser): do not require the unused classic reading to parse (writ…
claude 9d31e94
fix(parser): consume a connective before 'as' in start streaming resp…
claude 271d989
fix(P1): bound an active outbound read by the absolute stream deadline
claude 9919c35
test: build WflConfig with struct-update syntax (clippy field_reassig…
claude 60e6bda
fix(P1): make outbound streams handler-owned, closed on every exit path
claude 9cc7327
fix(P1): cancel a blocked upstream read when the downstream client di…
claude 1ea0d87
docs: outbound-stream deadline/ownership/disconnect now accurate (P1 …
claude fb737d9
test(P1): failing burst-disconnect regression for the concurrent loop
claude 3c1fb80
fix(P1): treat a client disconnect as cancellation, not a handler fai…
claude 2334272
style: cargo fmt (expand HttpClientError::Timeout, wrap assert)
claude 9a3ffb3
test(P1): failing coverage for property-then-index parsing
claude d938427
fix(P1): compose a bracket index after a .property/.method access
claude c931fba
test(P1): failing coverage for span-mismatched write fallback
claude bcf88c1
test(P1): failing coverage for absolute stream lifetime on buffered r…
claude 7fa2eb0
fix(P1): drop a span-mismatched classic write fallback
claude ed4764e
fix(P1): enforce the absolute stream lifetime on buffered reads
claude b760db3
test(P1): failing coverage for outbound cleanup on a dropped interpret()
claude 3ba8351
fix(P1): close outbound handles when the interpret() future is dropped
claude 0c750fc
test: bind server tests to an OS-assigned free port, not a hardcoded one
claude 825c215
test(P1): failing coverage for pre-head disconnect cancellation
claude 706200f
fix(P1): cancel a blocked upstream head open on client disconnect
claude 879fc36
ci: free runner disk in the heavy build jobs to avoid linker Bus errors
claude 87ab64c
docs: record re-review P1 lifecycle fixes (dev diary + design status)
claude a5518cc
test(P2): failing coverage for undefined in desugared write continuation
claude e11cdb6
fix(P2): analyze the shared continuation of a desugared ambiguous write
claude c19e67a
test(P2): failing coverage for streaming-operand type enforcement
claude e593378
fix(clippy): collapse the ambiguous-write lead guard into the match arm
claude daaf99e
fix(P2): enforce stream-handle operand types for wait/write/flush
claude c2bbbdb
test(P2): failing coverage for flush operand postfix accessors
claude a0ef057
fix(P2): compose postfix accessors onto merged-lead operands
claude bf700a9
docs: extend re-review dev diary with the P2 items (#7, #8)
claude e8c9712
fix(parser): anchor the '.' end-of-input diagnostic to the dot token
claude 0d6e8e1
test(P1): failing real-socket + unit coverage for the re-review blockers
claude 911ccc3
fix(P1): streaming lifecycle, cancellation, and ambiguous-write corre…
claude ec1af0a
ci(P1): run the documented integration gate and fix the Windows scripts
claude 04bbbb9
docs(P1): real-time outbound cap, streaming-write timeout, re-review …
claude b465600
fix(ci,docs): make run_integration_tests.sh executable; correct write…
claude 19bf271
refactor(parser): anchor the write `of` call to the `of` keyword
claude 5fd93a9
test(parser): Red coverage for write `of`-arg precedence + method-cal…
claude f41f6b7
fix(parser): consistent `of`-arg precedence and method-call postfix i…
claude b25aed5
docs: clarify how a concurrent handler failure reaches the client
claude 5e01e44
fix: improve concurrent server stability and streaming compatibility
logbie a262036
style: cargo fmt for CI gate
logbie fce5d86
fix: address #642 re-review blockers (lifecycle, timeouts, parser)
logbie 8e8be0f
fix: finalize issue #642 pass to harden server stability and streaming
logbie 09115f8
test: expose full streaming status operands
logbie 9935320
fix: parse complete streaming status operands
logbie f23fb6b
test: use valid streaming status fixture
logbie d97f15b
test: expose postfix loss after of calls
logbie 764685c
fix: resume postfix parsing after of calls
logbie c8cfa08
test: expose false type response boundary
logbie 485bc34
fix: remove false type response boundary
logbie 55f3d50
test: expose unmerged flush operands
logbie 4a83845
fix: dispatch unmerged flush operands
logbie 8b10f8b
test: expose response stream scope leaks
logbie a1bdd9d
fix: mirror runtime child scopes in type checker
logbie 7bafc6d
test: use valid try scope fixture
logbie 24f57d6
test: expose missing response stream type joins
logbie 046b012
fix: join conditional type states
logbie a30fe4f
test: expose missing local file handle types
logbie 370073e
fix: recreate local file handle types
logbie f0dc05d
test: exercise asynchronous file read type
logbie 85768c2
test: use unambiguous handle collision guard
logbie 0e98fe3
test: expose real file handle write fallback
logbie 5bef235
test: expose stale deadline after clean eof
logbie af800a7
fix: preserve clean eof after stream deadline
logbie 5d8fa3d
test: expose unbounded expired stream metadata
logbie c7f57b9
fix: bound expired outbound stream state
logbie 00a2a3f
refactor: satisfy formatter and lint gates
logbie 4c45f16
test: expose response expression disconnect stalls
logbie 0d4b26b
fix: cancel disconnected response evaluations
logbie 90a225d
refactor: make expiry tie handling deterministic
logbie edb8ce8
test: replace false-positive streaming lifecycle coverage
logbie 3bc38c6
test: expose residual response cancellation leaks
logbie c73260f
fix: close residual response cancellation gaps
logbie 7554ed1
test: complete issue 642 evidence coverage
logbie c9c748c
docs:correct-response-stream-scope-characterization
logbie 96d5305
test: expose retained clean eof stream state
logbie 68569b3
test: expose checker backedge and handler state gaps
logbie b32ff55
fix: terminalize clean eof streams immediately
logbie 527b8fb
fix: stabilize checker control-flow state
logbie 03966f0
test: expose eof latch and try scope races
logbie de34e32
fix: linearize eof and analyzer try state
logbie 81b2574
test: cover clean eof missing-slot races
logbie a543650
docs: record stream state review fixes
logbie 690be0a
fix: restore backward compatibility for classic syntax regressions
logbie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
Dev diary/2026-07-22-concurrent-handler-run-state-isolation.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # Dev Diary — 2026-07-22 — Per-handler run-state isolation for `main loop concurrently:` | ||
|
|
||
| ## Context | ||
|
|
||
| PR #641 shipped `main loop concurrently:` — opt-in cooperative concurrency for | ||
| HTTP request handlers, driven by a `FuturesUnordered` on the single interpreter | ||
| thread. Review (maintainer P1 #1, echoed by Copilot) flagged a soundness gap: | ||
| the concurrent loop isolated each handler's **environment** (variables), but the | ||
| interpreter's **run-state** — the count-loop variable and its flag | ||
| (`current_count` / `in_count_loop`), the live recursion depth (`call_depth`), | ||
| the diagnostic call stack (`call_stack`), and the current block's overload-dup | ||
| set — still lived on the shared `Interpreter` behind `RefCell`/`Cell`. | ||
|
|
||
| Under serial execution that state is never contended. Under | ||
| `main loop concurrently:` several handler futures interleave on one thread, so at | ||
| every `await` one handler's run-state was visible to — and overwritable by — | ||
| whichever sibling was polled next. A handler that yielded *inside a `count` loop* | ||
| would resume and read a `count` set by another handler. | ||
|
|
||
| ## The bug, concretely | ||
|
|
||
| `count` does not resolve through the environment while a count loop is active; | ||
| `try_evaluate_variable_sync` short-circuits on `in_count_loop` and reads | ||
| `self.current_count` directly. Both fields are global, so two concurrent count | ||
| loops share one `current_count`. A handler counting `1..5` that yields mid-loop | ||
| could come back reading `100..104` from a sibling. | ||
|
|
||
| ## Fix — a poll-swap wrapper (no `Rc`→`Arc`, no threads) | ||
|
|
||
| The interpreter core stays single-threaded and `Rc`-based (a hard constraint). | ||
| Rather than thread a per-handler execution context through every `&self` method, | ||
| each handler owns a `RunState` snapshot and an `IsolatedHandler` future wraps the | ||
| handler: | ||
|
|
||
| - On **each `poll`**, `swap_run_state` swaps the handler's `RunState` into the | ||
| interpreter's live fields (a field-by-field `mem::swap`, its own inverse). | ||
| - The inner handler future is polled. | ||
| - The instant `poll` returns — `Ready` **or** `Pending` — the state is swapped | ||
| back out into the handler's `RunState`. | ||
|
|
||
| So the interpreter's run-state fields become effectively poll-local: exactly one | ||
| handler's state is installed at a time, and a suspended handler's state is parked | ||
| in its own `RunState` where no sibling can touch it. Each handler starts from | ||
| `RunState::fresh(base_call_depth)`. The inner future is already wrapped in | ||
| `catch_unwind`, so a panic surfaces as `Ready` and the swap-back still runs, | ||
| leaving the scratch fields clean for the next sibling. | ||
|
|
||
| Serial execution is completely untouched — `IsolatedHandler` is used only by | ||
| `execute_concurrent_main_loop`. | ||
|
|
||
| ## Testing (Red → Green) | ||
|
|
||
| `tests/concurrent_main_loop_test.rs::test_concurrent_handlers_do_not_share_count_loop_state`: | ||
| two concurrent handlers each run a `count` loop over a **disjoint** range | ||
| (`1..5` vs `100..104`), yielding via `wait for` mid-iteration and then reading | ||
| `count`. With isolation each handler observes only its own range. | ||
|
|
||
| - **Red** (isolation bypassed — plain handler pushed to `FuturesUnordered`): | ||
| `/a` returned `100-101-102-103-104-`, i.e. it observed the *other* handler's | ||
| entire count range. `assertion left == right failed`. | ||
| - **Green** (isolation restored): `/a` → `1-2-3-4-5-`, `/b` → | ||
| `100-101-102-103-104-`. | ||
|
|
||
| Risk class **R3** (concurrency + lifecycle). The test asserts a concrete wrong | ||
| outcome under sharing, not merely "did not crash". | ||
|
|
||
| ## Follow-ups still open from the review | ||
|
|
||
| Larger P1 items remain and are tracked in | ||
| `Docs/development/concurrency-phase-plan.md`: immediate-500 on pre-respond | ||
| failure, browser-disconnect/504 cancellation threaded into `wait for` and | ||
| upstream reads, and an absolute total-stream deadline. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Make the testing-policy adoption status consistent across all governance files.
The agent guides describe
testing.mdas binding, while its embedded policy remains “Proposed” and effective only “Upon adoption.”AGENTS.md#L15-L21: state the local adoption status and effective date consistently.AGENTS.md#L125-L128: align the “Binding policy” wording with that status.CLAUDE.md#L17-L23: align the governance reference with the adopted status.CLAUDE.md#L163-L167: align the section title and introduction.testing.md#L137-L143: mark the policy adopted locally, or explicitly document why it remains proposed.📍 Affects 3 files
AGENTS.md#L15-L21(this comment)AGENTS.md#L125-L128CLAUDE.md#L17-L23CLAUDE.md#L163-L167testing.md#L137-L143🤖 Prompt for AI Agents