Repository navigation
Merged
Conversation
…ion throws - Scheduler: a failed timer check (CollectDue) waits for the next interval instead of stopping the host - WorkPump: a storage error taking the instance lock at startup is retried instead of stopping the host - MemoryTrimService, cache eviction, limiter monitor and job cleanup timers catch and log a failed run
…bjects - Stream the body through Utf8JsonWriter into pooled chunks instead of copying it into dictionaries and lists first - Read a plain PSCustomObject's instance members rather than PSObject.Properties, which builds member sets per object - Fall back to the existing serializer for shapes it does not reproduce exactly, so the output text is unchanged
…cket - the cache stores and serves bytes, so binary List* bodies are no longer cached empty - response triggers scan and parse the bytes
…n cannot exceed the table property limit
- FunctionsToExport and AliasesToExport filter a shared module's exports, ModuleVersion is reported, and a module that calls Export-ModuleMember is imported per worker - parse only the pools a node runs, HTTP modules first, root modules in parallel Fixes #52
- HttpBurstMax (default 0, per SKU profile, CRAFT_HTTP_BURST_MAX): a user request that finds every HTTP worker busy gets up to that many more, disposed again after HttpBurstIdleSeconds without one waiting - API clients stay capped at HttpPoolSize workers, so burst capacity is only used by interactive requests - no burst worker is built while the GC heap is above 75% of its limit - healthz reports HTTP worker counts; run-e2e-http-burst.ps1 covers it in the e2e suite
…nd BG load stacks
JohnDuprey
approved these changes
Oct 29, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Seven changes: background-failure resilience, cheaper large responses, orchestration and module-loading fixes, and on-demand HTTP workers for interactive users.
1. Keep background loops and timers running when an iteration throws (
2718534)An exception escaping a hosted service's
ExecuteAsyncstopped the whole host (BackgroundServiceExceptionBehaviordefaults toStopHost), and one escaping aSystem.Threading.Timercallback killed the process. A single out-of-memory error during a large request could restart the container even though the request had already failed cleanly.SchedulerService: a failed due-timer check is logged and retried on the next interval.WorkPump: a failure taking the instance lock at startup is retried every 5 s instead of faulting the pump.MemoryTrimService, cache eviction, the background limiter monitor and the job cleanup timer catch and log their own failures.2. Write JSON response bodies straight from the PowerShell objects (
2ed53ef)ConvertPsObjectToJsoncopied the whole result graph into dictionaries and lists and read every object throughPSObject.Properties, which builds PowerShell's reserved member sets on each row. The newPsJsonWriterwrites the JSON directly withUtf8JsonWriterinto pooled 1 MB chunks:PSCustomObjectis read through its own instance members.PSObject.Properties.Output is identical; tests compare it with the old path across 20 body shapes.
3. Keep response bodies as UTF-8 bytes from serializer to socket (
6cea274)ScriptResult.BodyBytesis now the body everywhere: the dispatcher writes it in one call, the response cache stores it on disk and in memory, and the trigger checks scan and parse it.Bodyremains as a decoding convenience, so a large body is no longer also held as a UTF-16 string.Also fixes a cache bug: a binary
List*response (e.g. an image) was cached as its empty text, so a cache hit returned 200 with an empty body.4. Truncate task error text in the finish transaction (
099f09a)A task's
LastErrorgoes into the finish transaction, which cannot split a property over 64 KiB. A very long error made the whole finish fail. It is now capped at 8,000 characters.5. Shared script modules export what their manifest lists (
7a86330, fixes #52)Workers instantiate each app module from one shared parse. That path exported every function (private ones included), no aliases, and reported version 0.0. It now matches a native manifest import:
FunctionsToExportandAliasesToExportfilter the module's exports, andModuleVersionis reported. Variables are not exported, matching a native import of a.psm1withoutExport-ModuleMember.Export-ModuleMemberitself is imported per worker, because the manifest then filters the module's own export call, whichNew-Modulecannot repeat.FunctionsToExportlist is compressed once per process into the shortest prefix patterns, becauseNew-Module -Functionmatches every function against every name (about 150 ms per worker on an 846-name list).6. On-demand HTTP workers for interactive requests (
ee58073,fd14e21)New
Worker:HttpBurstMax(default 0, off), also settable perSkuProfilesentry and throughCRAFT_HTTP_BURST_MAX:HttpBurstMaxbeyondHttpPoolSize, at most two at a time.HttpPoolSizeworkers at once, so burst capacity is only used by interactive requests. When burst is off, no cap is applied.HttpBurstIdleSeconds(default 600) without a user waiting, burst workers are disposed one per tick, back down toHttpPoolSize./healthznow reportsworkers.httpandworkers.httpAvailable. Pool metrics and the background limiter's HTTP-pressure check use the live worker count.HttpBurstMax.Results
Serializer benchmark, 20k rows (
CRAFT_BENCH=1,ResponseJsonBench):End to end on a container, a 362 MB response (100k rows): peak container memory went from 2,680 MB to 2,220 MB. Wire bytes are unchanged, except br is ~3% smaller on large bodies because the encoder gets the body in one piece.
Startup on a production-layout app (Linux container, time to HTTP ready):
An extra HTTP worker builds in ~125–170 ms with ~2.2 MB of heap, so burst workers come up quickly.
Client impact
Location,Content-Disposition, custom headers) are unchanged. The internalX-Craft-Endpointtag is still removed before sending.FunctionsToExportare no longer callable from outside that module. An app that called such a helper from another module must export it. This is the same rule a native manifest import always applied.HttpBurstMaxis set.Testing
Export-ModuleMemberHttpBurstMax, idle workers are trimmed back to the poolHttpBurstMaxapplies only when set; a long task error no longer fails the finish transactionAccept-Encoding, as a cache miss and a hit. Every compressed response was decoded and compared to the original.image/jpegbytes, a 302 withLocation,text/plain,application/pdfwithContent-Disposition, custom headers, and pre-serialized JSON strings.run-e2e.ps1): 102 of 103 pass.http-burstsection, also runnable with-BurstOnly: a user call is answered in 114 ms while API clients hold both workers, the pool grows pastHttpPoolSize, API clients run two at a time with a burst worker free, and the pool trims back when idle.orch-status summary-bounded, also fails on an unmodifieddevimage. It is a timing-sensitive check, not caused by this PR.