Follow-up to objectstack#12938 / PR objectstack#12952, found while running the
consumer acceptance that PR named (objectstack-ai/cloud#1710).
Measurement
ObjectStack Cloud's migrate-control-db.yml, plane=staging, apply=false, against
the real Neon staging control plane, framework pinned at 15d55fb2430f (carries
fc8a33935). Run: objectstack-ai/cloud actions run 33182129326.
The command is os migrate plan inside docker run --rm, cwd
/repo/cloud/apps/cloud (an objectstack.config.ts is present, so the host
stack is composed). The CLI finished and said so:
15:03:52.522 17 change(s): 0 safe, 0 needs-confirm, 17 destructive
15:03:52.522 Apply with: os migrate apply (add --allow-destructive for drops / tightenings)
15:03:52.522 4316ms
15:03:52.517 INFO Graceful shutdown started
15:03:52.555 INFO OK Graceful shutdown complete
Then nothing. The next line in the log is 16:22:40 The operation was canceled. — the run was cancelled by hand after 78 minutes. The shell step's
very next statement is an echo, and it never printed, so the shell was still
blocked on that one docker run: the process printed its own graceful-shutdown
line and then did not exit.
Elapsed inside the CLI was 4.3 seconds. Everything after that is a process that
will not die.
Why this is a fix-here defect, not a workflow defect
The composition objectstack#12952 introduced runs host plugins for their
DECLARATIONS: init() runs, start() is replaced with a no-op, destroy() is
forwarded. A host plugin that acquires a resource in init() — a pg pool, an
interval, a socket, a watcher — and releases it from something registered inside
start() now has no release path at all, because the thing that would have
registered the release never ran. The event loop stays alive; process.exit is
never reached; the kernel has already reported a clean shutdown, so nothing looks
wrong.
That is a direct consequence of the declaration-only composition and it is
invisible to the CLI's own test suite, which does not assert that the process
exits. The measured shape — 36 host plugins composed, clean shutdown logged,
process alive 78 minutes later — is the first real deployment this composition
has been run against.
Consequence
os migrate plan and os migrate apply never return on a project with a host
config. For cloud's operator workflow that means the job hangs until the job
timeout (six hours by default) after the plan has already been computed and
printed, and any consumer that reads the --json payload — cloud's coverage gate
is one — never gets to run at all.
Suggested shape
Two candidates, not adjudicated here:
- Make the declaration-only composition symmetric: if
start() is suppressed,
the composition owes the teardown its suppression removed. destroy() is
already forwarded, so the seam exists.
- Have
plan / apply exit deliberately once the document is written, rather
than relying on the event loop draining. That is a smaller change and it also
covers host code this repo cannot audit, which is the same argument
objectstack#12952 used for declaration-only composition in the first place.
Filed without an assignee.
Follow-up to objectstack#12938 / PR objectstack#12952, found while running the
consumer acceptance that PR named (objectstack-ai/cloud#1710).
Measurement
ObjectStack Cloud's
migrate-control-db.yml, plane=staging, apply=false, againstthe real Neon staging control plane, framework pinned at
15d55fb2430f(carriesfc8a33935). Run: objectstack-ai/cloud actions run33182129326.The command is
os migrate planinsidedocker run --rm, cwd/repo/cloud/apps/cloud(anobjectstack.config.tsis present, so the hoststack is composed). The CLI finished and said so:
Then nothing. The next line in the log is
16:22:40 The operation was canceled.— the run was cancelled by hand after 78 minutes. The shell step'svery next statement is an
echo, and it never printed, so the shell was stillblocked on that one
docker run: the process printed its own graceful-shutdownline and then did not exit.
Elapsed inside the CLI was 4.3 seconds. Everything after that is a process that
will not die.
Why this is a fix-here defect, not a workflow defect
The composition objectstack#12952 introduced runs host plugins for their
DECLARATIONS:
init()runs,start()is replaced with a no-op,destroy()isforwarded. A host plugin that acquires a resource in
init()— a pg pool, aninterval, a socket, a watcher — and releases it from something registered inside
start()now has no release path at all, because the thing that would haveregistered the release never ran. The event loop stays alive;
process.exitisnever reached; the kernel has already reported a clean shutdown, so nothing looks
wrong.
That is a direct consequence of the declaration-only composition and it is
invisible to the CLI's own test suite, which does not assert that the process
exits. The measured shape — 36 host plugins composed, clean shutdown logged,
process alive 78 minutes later — is the first real deployment this composition
has been run against.
Consequence
os migrate planandos migrate applynever return on a project with a hostconfig. For cloud's operator workflow that means the job hangs until the job
timeout (six hours by default) after the plan has already been computed and
printed, and any consumer that reads the
--jsonpayload — cloud's coverage gateis one — never gets to run at all.
Suggested shape
Two candidates, not adjudicated here:
start()is suppressed,the composition owes the teardown its suppression removed.
destroy()isalready forwarded, so the seam exists.
plan/applyexit deliberately once the document is written, ratherthan relying on the event loop draining. That is a smaller change and it also
covers host code this repo cannot audit, which is the same argument
objectstack#12952 used for declaration-only composition in the first place.
Filed without an assignee.