Repository navigation
Conversation
🦋 Changeset detectedLatest commit: ccef2c5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
352353b to
8692445
Compare
f734aff to
5e689b0
Compare
8692445 to
eab3b67
Compare
5e689b0 to
b153a86
Compare
b153a86 to
5f995eb
Compare
eab3b67 to
5cff4a5
Compare
a310cc5 to
96d3dea
Compare
A host module that holds state for a run, such as a browser session
it opens on the first call, had no way to learn when to let go of
it. A run waiting in `ctx.interrupt()` releases its isolate and later
starts again from the top, and a run that ends gives no signal to the
modules it called. Host modules are built once per connection, so
each author would have to keep per-run state keyed by id themselves.
`createScopedModule({ exports, start })` makes a host module with one
instance for each scope of a run. A scope lasts from when the run's
code starts in an isolate until it waits in `ctx.interrupt()` or
ends, so a run that resumes gets a new scope and a new instance. The
backend calls `start` with the run's id and metadata on the scope's
first call to the module, and calls made meanwhile share that start,
so it gets one signal for the scope, which aborts when the scope is
cancelled or released. A call answered from the run's journal
doesn't start an instance. The export names are listed up front
because the backend builds the module graph before any run, and a
call fails if the functions `start` returns don't match them. The
module is marked by `createScopedModule()` rather than recognized by
shape, since an object module's keys are its export names.
An instance closes if and only if it started. It closes with reason
"paused" when a waiting run has released its isolate and the calls it
was making have settled, before the run can resume, so an answer that
arrives meanwhile resumes it only after the close. It closes with the
run's terminal reason after its host calls settle and before its exit
is published. A run killed while paused has nothing open, so nothing
closes. Every instance of a scope closes at once under one
`maxHostCallMs` deadline, and a close that throws or runs late is
reported with `console.error` without changing how the run ends.
Only a terminal close gets `log()`, which appends to the run's
stderr. Logged text is saved in the same transaction as the run's
terminal state rather than appended as it arrives, so a run
interrupted between the two never resumes and replays against output
it didn't write. It counts toward `maxStdioBytes`, and text past the
limit is cut and followed by a line saying so.
Releasing a paused run now waits for its isolate to stop rather than
for its completion callbacks, which may be settling the run and wait
for the release in turn. A run that ends while it is being released
waits for that release, including the calls it drains, before its
exit is published.
96d3dea to
ccef2c5
Compare
|
@ben-reitz Spent a bit of time reviewing this with an agent. The biggest bit for me is the addition of the new This is sorta where I got to in my session: The module kinds differ mainly in how long their state lives:
Smaller things in this PR
|
|
Replaced by #301, which now carries this commit. |
A host module that holds something for a run, such as a browser session, can't tell when to let go of it. A run waiting in
ctx.interrupt()gives up its isolate and later replays from the top, and a run that ends tells its modules nothing.createScopedModule()adds a host module whose state lives for one scope of a run: from when the run's code starts in an isolate until it waits inctx.interrupt()or ends. A run that resumes starts a new scope. The backend callsstarton the scope's first call to the module and closes the instance when the scope ends:The export names are listed up front because the backend needs them before any run starts, and a call fails if
startreturns different functions. Concurrent first calls share one start, sostartgets its ownsignal, which aborts when the run is killed. Only a final close canlog(), and that text is saved in the same transaction as the run's end, so a restart can never replay a run against output it didn't write. Plain modules and factories work as before.Ending a run now also waits for a pause's release to finish, so killing a run as it pauses can't publish its exit while the isolate is still shutting down.
Unit and workerd tests cover each moment in the diagram, plus a shared first start, an exports mismatch, a kill during
start, a close that throws, and output cut atmaxStdioBytes.docs/17_isolate_javascript.mdhas a new section on scoped modules.