Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions adoption/bootstrap-macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
# mapfile, no associative arrays, no ${var,,}.
set -Eeuo pipefail

# Round 3k: see the identical export in adoption/launchd/launchd-agents.sh
# for why (offline fault-injection tests only; no production path reads
# this).
export ADOPTION_SCRIPT_PID="$$"

usage() {
printf '%s\n' \
'Usage: bash bootstrap-macos.sh --profile <id> [--skip-system-packages]' \
Expand Down
11 changes: 11 additions & 0 deletions adoption/launchd/launchd-agents.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
# variable is used instead).
set -Eeuo pipefail

# Round 3k (coordinator, WSL-host CPU-load flakiness fix): exported purely
# for offline fault-injection tests. A shim that must signal THIS script's
# own top-level process reads this instead of $PPID -- $PPID is correct
# today for every foreign command this script calls directly (none of
# them run inside a $(...) or a pipeline, so $PPID already equals $$ at
# each call site), but that is not an invariant future edits are
# guaranteed to preserve; bootout_and_wait's own subshell-vs-plain-call
# comment below records exactly how that assumption broke once already.
# No production code path reads this variable.
export ADOPTION_SCRIPT_PID="$$"

usage() {
printf '%s\n' \
'Usage: bash launchd-agents.sh render|lint|install|status|remove [options]' \
Expand Down
16 changes: 8 additions & 8 deletions manifests/evidence.json
Original file line number Diff line number Diff line change
Expand Up @@ -3144,8 +3144,8 @@
},
{
"path": "adoption/bootstrap-macos.sh",
"sha256": "4de962dbcd13ea7878c17c24562aad582980ebf47f687e7ed8049e3ab7d8e511",
"bytes": 57933
"sha256": "c12ec11732ec79de29808155af3ae6029850b9d008f3b9add73ac14db41ef9f6",
"bytes": 58122
},
{
"path": "adoption/bootstrap.md",
Expand Down Expand Up @@ -3189,8 +3189,8 @@
},
{
"path": "adoption/launchd/launchd-agents.sh",
"sha256": "1a7508626e59509ee97bfc2517463ba9ed771f5279ca5cfa7abc119403deba3e",
"bytes": 37066
"sha256": "8d607da363de3a92552e76c88043fc0576168a015e5f30898d07ee3cf4839e6e",
"bytes": 37719
},
{
"path": "adoption/lifecycle.md",
Expand Down Expand Up @@ -27745,8 +27745,8 @@
},
{
"path": "tests/test_adoption_bootstrap_macos.py",
"sha256": "747420333a546becb4504a993bcda06bc514a82dae0183f78c6029bcee7c6f2b",
"bytes": 161914
"sha256": "adb957701308abd587555a3161791e4c53ab56747aa768b594f03d5d15949e36",
"bytes": 165300
},
{
"path": "tests/test_adoption_contract.py",
Expand All @@ -27760,8 +27760,8 @@
},
{
"path": "tests/test_adoption_launchd.py",
"sha256": "78f2190610770ff7af561886dbb63ea0e70c2afae2e100c9405109e4107f4b0f",
"bytes": 97478
"sha256": "f50a8f8e3106e0ec34151dff4ac549c60e19f1109e3555fab7fad689af5f34c2",
"bytes": 111506
},
{
"path": "tests/test_adoption_status.py",
Expand Down
101 changes: 79 additions & 22 deletions tests/test_adoption_bootstrap_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,53 @@ def _shell_functions(text: str, *names: str) -> str:
return "".join(blocks)


def _signal_and_wait_for_exit(signal_name: str, indent: str = " ", target_var: str = "ADOPTION_SCRIPT_PID") -> str:
"""Round 3k: a fault-injection shim's real-command-then-signal tail.

Signals `target_var` -- the harness's own top-level PID, exported as
`ADOPTION_SCRIPT_PID` right after its `set -Eeuo pipefail` (see
_run_install_npm), never `$PPID` (that half is genuinely
load-independent: see tests/test_adoption_launchd.py's
SignalShimMechanismProofTests.test_ppid_inside_a_command_substitution_
subshell_is_not_the_script for a deterministic, no-load reproduction).

Round 3k's FIRST draft of this helper also polled `kill -0 "$target"`
in a loop, waiting for the target to have fully exited before this
shim itself returns. That design was WRONG and is not used: this shim
is always the target's own SYNCHRONOUS foreground child (exactly how
real npm/mv/ln are invoked), so the target cannot possibly exit --
its own `wait()` cannot return -- while still blocked waiting for THIS
shim to finish. A shim that waits for the target to disappear before
it disappears itself is circular, not a handshake; it does not fail
loudly either, since its own 30s bound always fires (proven directly:
every affected test took ~30-63s per run in this project's first
commit of this helper, passing only because the shim's own timeout
path, not a real interruption, produced a nonzero exit the assertions
happened to accept as "not 0").

What actually determines correctness, confirmed by direct, repeated
(bash 5.2, this host, 10/10 both with and without an added sleep)
testing: bash defers running a trapped signal's handler until whatever
is CURRENTLY in the foreground finishes -- here, this shim itself --
and then services it immediately afterward, before the script's own
next line, regardless of how long that takes. Ordering is therefore
already guaranteed by bash's synchronous foreground-child execution
model alone, once the signal reaches the right PID (the fix above).
This tail still sleeps briefly (0.5s, comfortably above the 0.2-0.3s
this project's earlier rounds used) purely as defensive margin for the
real `kill()` syscall's effect to be recorded before this process
image goes away -- not to "win a race" against the trap, which this
shim structurally cannot observe completing. Same helper (and the
same design) as tests/test_adoption_launchd.py's own copy.
"""
p = indent
return (
f'{p}target="${target_var}"\n'
f'{p}kill -{signal_name} "$target"\n'
f'{p}sleep 0.5\n'
)


class LlamaWrapperQuotingTests(unittest.TestCase):
"""Local integration check of the generated llama-server wrapper.

Expand Down Expand Up @@ -1231,6 +1278,11 @@ def _run_install_npm(self, tmp_path: Path, pins_path: Path, id_: str, version: s
harness = tmp_path / "install-npm-e2e-harness.sh"
harness.write_text(
"set -Eeuo pipefail\n"
# Round 3k: exported for this harness's own fault-injection
# shims (npm/mv/ln) to signal deterministically -- see
# _signal_and_wait_for_exit's own comment for why, never
# $PPID.
+ 'export ADOPTION_SCRIPT_PID="$$"\n'
+ _shell_functions(SCRIPT_PATH.read_text(), "canonical_path", "prune_old_version",
"npm_package_name", "install_platform_dependency", "install_npm", "cleanup")
# The script-level pending_migration_prefix/_dest globals and the
Expand Down Expand Up @@ -1261,7 +1313,13 @@ def _run_install_npm(self, tmp_path: Path, pins_path: Path, id_: str, version: s
+ "}\n"
+ f'install_npm {id_} {version} {wrapper_url} {wrapper_sha256} {ignore_scripts}\n'
)
result = subprocess.run(["bash", str(harness)], capture_output=True, text=True, timeout=60,
# Round 3k: 90s, not 60s -- a fault-injection shim's own bounded
# wait for the script to react to a signal (_signal_and_wait_for_
# exit) can itself take up to 30s under real CPU load before it
# gives up; this must stay comfortably above npm install's own
# time plus that 30s so a genuine slow-but-eventual pass is never
# mistaken for a hang.
result = subprocess.run(["bash", str(harness)], capture_output=True, text=True, timeout=90,
env={**os.environ, **(extra_env or {})})
return result, eco_root

Expand Down Expand Up @@ -1906,21 +1964,22 @@ def test_signal_between_the_one_time_migration_and_the_flip_restores_the_real_di
'case "$dest" in\n'
" *.migrating.*)\n"
' /bin/mv "$@" || exit $?\n'
f' kill -{signal_name} "$PPID"\n'
# Stay alive briefly after sending the signal: the
# parent bash is blocked in wait() for THIS process,
# and a signal delivered while it is still blocked
# there reliably interrupts that wait immediately;
# if this shim exited instantly instead, bash's
# wait() could already be returning (child exited
# normally) around the same moment the kernel is
# still only queuing the signal, letting bash race
# ahead through the rest of the flip -- successfully
# -- before ever servicing it, which would make this
# test flaky rather than a reliable reproduction of
# the coordinator's exact "signal mid-swap" scenario.
" sleep 0.2\n"
" exit 0\n"
# Round 3k: was a fixed `kill -SIG "$PPID"; sleep
# 0.2; exit 0`, wrong on two counts fixed since --
# see _signal_and_wait_for_exit's own docstring for
# both. What this line now does: signal
# ADOPTION_SCRIPT_PID (never $PPID), then pause
# briefly (0.5s, a fixed defensive margin, not a
# wait for anything specific) before this shim
# itself returns. It does NOT block until the
# parent has exited -- this shim is the parent's
# own synchronous foreground child, so the parent
# cannot exit while still waiting on it; ordering
# is guaranteed instead by bash servicing the
# pending trap immediately once this shim (its
# current foreground child) naturally returns.
+ _signal_and_wait_for_exit(signal_name, indent=" ")
+ " exit 0\n"
" ;;\n"
"esac\n"
'exec /bin/mv "$@"\n'
Expand Down Expand Up @@ -2084,9 +2143,8 @@ def test_a_signal_during_npm_install_leaves_final_prefix_untouched(self):
"#!/bin/sh\n"
'if [ "$1" = "install" ]; then\n'
f' {real_npm} "$@" || exit $?\n'
f' kill -{signal_name} "$PPID"\n'
" sleep 0.3\n"
" exit 0\n"
+ _signal_and_wait_for_exit(signal_name, indent=" ")
+ " exit 0\n"
"fi\n"
f'exec {real_npm} "$@"\n'
)
Expand Down Expand Up @@ -2222,9 +2280,8 @@ def test_a_signal_after_creating_tmp_link_leaves_final_prefix_untouched(self):
f'case "$dest" in\n'
f" {stage_dir}/*-link.*)\n"
' /bin/ln "$@" || exit $?\n'
f' kill -{signal_name} "$PPID"\n'
" sleep 0.3\n"
" exit 0\n"
+ _signal_and_wait_for_exit(signal_name, indent=" ")
+ " exit 0\n"
" ;;\n"
"esac\n"
'exec /bin/ln "$@"\n'
Expand Down
Loading
Loading