watchdog in daslang: a process handle in fio, whole-program standalone emission, watchdog.py retired - #3950
Conversation
…ait/terminate/kill/pid/alive, close_process - the primitive a supervisor drives on its own clock
popen_argv is block-scoped and blocks to EOF; a supervisor needs a handle it
can poll and drain on a 250 ms tick, signal, and reap. Nine externs in fio_core
next to popen_argv, an opaque `SubProcess?` handle, and a `process_running`
sentinel that process_poll / process_wait answer while the child is alive.
- spawn_process(argv, cwd, env): no shell, forward-slash argv[0] on every
platform; `cwd` empty inherits; `env` is KEY=VALUE overrides on the
inherited environment. stdout+stderr merge into one non-blocking read end.
Windows: kill-on-close job object, exe resolved from the parent's directory
as CreateProcess does. POSIX: the child leads its own process group so
killpg reaches the tree; a relative argv[0] naming a path is made absolute
before the child chdir's, matching Windows.
- process_drain(p, blk): fires blk once per complete line ready this tick
(partial lines buffer across calls, a final unterminated line flushes at
EOF); returns false once stdout is closed. Never blocks the tick.
- process_terminate / process_kill: TerminateJobObject 15 / 9 on Windows,
killpg SIGTERM / SIGKILL on POSIX.
- close_process: frees the handle; a still-running child dies with it
(job close on Windows, SIGKILL + reap on POSIX), so nothing leaks or zombies.
- daslib/fio: `with_process(argv[, cwd, env]) <| $(var p) {}` RAII over
spawn/close; `process` typedef.
tests/fio/test_process.das drives a daslang child through the whole
lifecycle - env and cwd observed in its output, lines drained one per call,
running while parked, the real exit code from wait, alive before and dead
after - and terminates a parked one. The fixture returns its code from main:
a das `exit()` unwinds as an abnormal termination and the CLI reports 1.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…state machine the host ticks, with the Python's config, discovery, stages, crash bundles and exit-code contract
The library half of the watchdog.py replacement. One module, no Python:
- WatchdogConfig is a [CommandLineArgs] struct. watchdog.json beside the
program sets each flag's default (a key becomes a `--flag value` the CLI
did not supply), an unknown key is fatal by name, and everything after
`--` goes to the child verbatim - clargs drops flag-shaped positionals,
so the split is explicit. Discovery: main.das beside bin/Release/daslang
is script mode, one *.exe is program mode, anything else is an error.
- Emitter writes {"ts","event",...} as one compact JSON line per record
to a file that rotates at 20 MB x 5, and to stdout.
- StageDetector ranks the child's startup lines and reports only forward
moves; the tune-restart marker rewinds the rank. @tune events log at the
kernel boundaries. Health and shutdown go over dasHV's client.
- Crash bundles: crash.json, the log, the dump, symbols, the tune sidecar,
the JIT artifacts the child named. WER policy read/install via reg.exe.
- Supervisor.tick() is one 250 ms step: drain, health, poll, classify an
exit - 0 ends, 3 with the marker relaunches at once, 3 without backs off,
4 relaunches, anything else is a crash with bounded backoff. The host
owns the loop and calls request_stop() on its signal; run() is the loop
for a host that owns nothing else. watchdog_start() does everything
before the loop: config, the dump policy, the single-instance pid file.
Cut on purpose: the exchange UI (consent, sidecar fold, tray rails, the
DAS_TUNE_CONTROL stop file) and the tray; notifications stay, spawned
through the platform's own helper, no binding needed.
tests/watchdog drives the library under dastest with a daslang child:
config layering, discovery, stage ranks, the log envelope and rotation,
crash-bundle-restart, config restart, the script-mode tune bootstrap,
and the stop-file handshake from a host stop request.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ext is one translation unit, and a required module's functions, its class methods and the modules their externs reach used to be left out A `-ctx` context walked only the entry module: `collectUsedFunctions` ran with `all_modules = false` on every standalone path, the markers, the block-variable collector, the `ArgsConverter` thunks and the emitter all visited `thisModule` alone, and the module registration marked only what the entry module's own code named. A required das module's functions were neither declared nor defined, its class methods had no table row for their Func slot, and a C++ module reached only from one of them (dasHV from a supervisor's health probe) was neither included nor registered. The example survived because its one cross-module das call was inlined; the cross-module fixtures covered globals and types, never a call. Now `foreignUsedFunctions` (aot_cpp) lists the used, non-builtin, non-template functions outside the entry module, and every pass walks them after the entry module through the bound `visit(fn, adapter)`: NoAotMarker (before the can-AOT check, so its verdicts are final), PrologueMarker, SetPrinterFlags, the collector, `UseTypeMarker` where the standalone path prunes and links, the thunks, and `StandaloneContextGen`. Declarations, the function count, the debug-info rows and the table are whole-program. A generic instance several modules instantiated shares one AOT name, so declarations, bodies, thunks and rows are emitted once per name. Foreign globals' initializer temporaries collect under the collector's null key, the one `__init_script` declares. tests/aot: `_standalone_foreign_call_fixture` calls a required module's function and a class method through its Func slot; the arm asserts both rows, the body, and that fio_core - reached only from the foreign function - is registered. The example and the C++ standalone-context tests are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the full runtime with libDaScript and dasHV static, driven one tick at a time by a C++ main that owns argv, the pid and the signals utils/watchdog/main.das is the entry for two hosts. The exe host calls start / tick / request_stop / result: main.cpp hands argv to setCommandLineArguments, installs SIGINT/SIGTERM (SIGBREAK on Windows) into an atomic the loop turns into request_stop, passes its pid for the single-instance file, and returns the supervisor's code. argv[0] locates the bundle: watchdog.json and the discovered program sit beside the executable, where the Python sat beside what it supervised. The interpreter host runs main: the same supervision, --cwd or the current directory as the program directory, no pid file, no signal hook. utils/CMakeLists.txt builds it the way examples/standalone/06 is built: `utils/aot/main.das -- -ctx` generates the context, the exe links libDaScript and libDasModuleHV, lands in bin/, strips in Release, installs beside daslang. watchdog_smoke (small lane) runs the binary supervising a daslang child that crashes once and exits clean, and reads the log back. `--help` prints the flag table; everything after `--` goes to the child. CLAUDE.md: `exit(N)` under the daslang CLI reports 1 whatever N was - a code the parent must read comes from `def main() : int`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…daspkg's new release_include_tool, the deploy scripts launch it, and the docs, skills and changelist follow
The four manifests (dasllama-server, dasllama-ladder, dasweb-buildd,
dasweb-playground) replace `release_include_from("utils/watchdog/watchdog.py")`
with `release_include_tool("watchdog")`: daspkg resolves `bin/<name>` or, in an
MSVC tree, `bin/Release/<name>.exe`, and copies the executable to the bundle
root, failing the release when no build has it (`ship_tools`, tested).
The systemd unit runs `<release>/watchdog`, deploy-jit.ps1 copies
`bin\Release\watchdog.exe` beside watchdog.json, the latchpoint launchers run
the built binary, and the "tray wording" Python CI step goes with the tests
of the features the port cut.
utils/watchdog/README.md and doc/source/reference/utils/watchdog.rst describe
the daslang watchdog; REVIEW.md binds the log's event vocabulary and the
flag table instead of the consent wording that no longer exists here. The
exchange review rule and the consent notice lose their watchdog copy.
fio's nine process externs, the SubProcess type and process_running get
their handmade entries; the shipped fio skill and cpp_integration carry the
new contracts; tests_in_repo drops a stale note about in-file no_aot.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the JIT artifacts' relative layout, an unassignable Windows job never shadows the process handle Three defects from review. A child that ignored SIGTERM held the supervisor forever: after the stop timeout the request now escalates to process_terminate once and to process_kill ten seconds later, each logged. The bundle computed a JIT artifact's path under jitted_scripts/ by chopping the jit dir's length off the source, which left a leading separator that path_join took as absolute; it now uses relative_result, which also refuses a sibling directory sharing the prefix. On Windows, a job object the child could not be assigned to (the watchdog itself inside a restrictive job) stayed on the handle empty, so terminate and kill acted on nothing; the job is closed and the process handle takes over. The crash test's child now names a cached DLL under a subdirectory of the jit dir, and the test asserts the DLL and its sibling artifact land in the bundle at that relative path. utils/daspkg/test_daspkg.das joins run_utils_tests: its unit suite ran in no CI row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… handle is proven to kill, three checklists say what they mean, and the bundled watchdog.json loses its retired key daspkg's ship_tools refused nothing: a manifest tool name went straight into `bin/<name>` and `<bundle>/<name>`, so a name with a separator read and wrote outside both directories. It now passes is_safe_pkg_name first (tested), and a shipped tool gets the rpath scrub and the symbols every other shipped binary gets. fio's "closing kills a child still running" was a C++-decided contract no test failed on; tests/fio now closes a handle - explicitly and by leaving a with_process block - while the child runs, and asserts the pid is dead. utils/dasllama-server/watchdog.json still carried the Python-era `tray` key, and the daslang watchdog refuses an unknown key: that bundle's supervisor would not have started. The key is gone, and the watchdog checklist gains the rule that would have caught it - a removed config field updates every watchdog.json in the tree. Its other rules now bind the log, not the file, and name the in-tree readers; utils/daspkg's rules name the release path, the sink property behind is_safe_pkg_name, and the main exe the sidecar rule means; daslib's struct-name rule names the property (anything reaching aotStructName / aotEnumName) instead of one visitor class. Drift the port left behind is settled: the shipped daspkg skill loses a `bin/Release/` the bundle gate bans and a self-defeating example, the shipped fio skill's sample compiles (every process_* call is unsafe), aot_testing.md drops the retired two-marker no_aot rule, and the dasLLAMA engine doc and server README stop naming a watchdog dialog. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… config's precedence needs no lookup, and the tuner abort, the --no-shutdown stop, the wait timeout and the paired table counts each get their test finish() carried a terminate/kill ladder no call site could reach - every caller nulls the child first, and the stop ladder in tick() is what ends a live child - so the arm is gone and the contract is stated on the function. config_to_args skipped a config key the command line also carried, but clargs keeps the last occurrence of a flag, so ordering the config's copy first already makes the flag win; the lookup is gone. The discovery test now pins the daslang beside main.das, not any string ending in "daslang". New arms: exit 3 without the restart marker is a tuner abort - relaunched with backoff, no crash; a stop under --no-shutdown terminates the child and still ends with 0; process_wait answers process_running when its timeout passes first; and the emission test checks the context sizes its function table by exactly the info rows it fills, foreign rows included - the two counts are computed separately and reverting either alone was green before. The park fixture parks without a stop file named, until terminated. The exchange client's comments and the exchange_accept environment doc stop naming the deleted watchdog dialog and its tray rail. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ers the file, not the utils/watchdog module it requires, so the AOT sweep still failed the link on that module; the skills now say so Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…bin/watchdog --cwd utils/dasllama-server supervises the source tree the way the README promised Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The new Windows spawn_process path passes a const std::string buffer to CreateProcessA via (LPSTR)cmdLine.c_str(), which is undefined behavior and needs a mutable command-line buffer.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR replaces the Python-based watchdog supervisor with a daslang implementation that can ship as a static executable, while also extending fio with a long-lived child-process handle API and updating the standalone (-ctx) emitter to include all used functions across required modules.
Changes:
- Add a daslang watchdog (
utils/watchdog/*.das) plus abin/watchdogstatic executable host (C++ main + standalone context generation) and retire the Python watchdog + tests. - Introduce
fiolong-lived process APIs (spawn_process,process_drain/poll/wait/...,process_running,with_process) and document them. - Update standalone-context emission to include all used functions across modules (including foreign functions/method slots) and extend tests/packaging to ship the watchdog tool via
release_include_tool.
File summaries
| File | Description |
|---|---|
| utils/watchdog/watchdog.das | New watchdog supervisor library (config/discovery, logging, stages, crash bundles, supervision loop). |
| utils/watchdog/test_tray_state.py | Removes Python tray wording tests (tray surface retired). |
| utils/watchdog/test_consent.py | Removes Python consent tests (dialog surface cut). |
| utils/watchdog/smoke_test.cmake | Adds CTest smoke coverage for the watchdog executable supervising a child through crash + clean exit. |
| utils/watchdog/REVIEW.md | Updates review checklist for log/stage name stability and config-field discoverability. |
| utils/watchdog/README.md | Updates documentation from Python supervisor to static watchdog executable + interpreter entry. |
| utils/watchdog/main.das | New shared entrypoint used by both the standalone exe host and interpreter mode. |
| utils/watchdog/main.cpp | New standalone executable host driving the supervisor via exported functions. |
| utils/watchdog/dummy_server.py | Removes Python dummy supervised server (desktop-surface testing retired). |
| utils/internal/dasweb-playground/README.md | Updates production launch instructions to use ./watchdog. |
| utils/internal/dasweb-playground/.das_package | Switches bundle shipping from release_include_from(watchdog.py) to release_include_tool("watchdog"). |
| utils/internal/dasweb-buildd/README.md | Updates production launch instructions to use ./watchdog. |
| utils/internal/dasweb-buildd/.das_package | Switches bundle shipping to release_include_tool("watchdog"). |
| utils/internal/dasllama-ladder/README.md | Updates deploy narrative to watchdog binary. |
| utils/internal/dasllama-ladder/dasllama-deploy.sh | Updates systemd unit to start the watchdog executable. |
| utils/internal/dasllama-ladder/.das_package | Switches bundle shipping to release_include_tool("watchdog"). |
| utils/daspkg/test_daspkg.das | Adds unit test coverage for ship_tools / release_include_tool behavior and safety checks. |
| utils/daspkg/REVIEW.md | Expands release-path review guidance to include tool shipping and path/shell safety. |
| utils/daspkg/package_runner.das | Plumbs tools through PackageReleaseInfo. |
| utils/daspkg/commands.das | Implements ship_tools and wires it into cmd_release. |
| utils/dasllama-server/watchdog.json | Removes stale tray key (now invalid). |
| utils/dasllama-server/test_openai_server.das | Updates reference from watchdog.py to utils/watchdog. |
| utils/dasllama-server/README.md | Updates launch instructions to watchdog executable; trims removed features. |
| utils/dasllama-server/deploy-jit.ps1 | Updates deploy script to copy watchdog.exe and update instructions. |
| utils/dasllama-server/CONTROL_PAGE_PLAN.md | Updates references to watchdog relaunch handling. |
| utils/dasllama-server/.das_package | Switches to release_include_tool("watchdog"). |
| utils/CMakeLists.txt | Adds build + install of watchdog executable and registers a smoke test. |
| tests/watchdog/test_watchdog.das | Adds watchdog behavior tests (config layering, discovery, stages, crash/restart, stop ladder paths). |
| tests/watchdog/_fixture_watchdog_child.das | Adds supervised child fixture for watchdog tests. |
| tests/fio/test_process.das | Adds tests for new long-lived process handle lifecycle and semantics. |
| tests/fio/_fixture_process_child.das | Adds supervised child fixture for fio process tests. |
| tests/aot/test_standalone_emit.das | Adds coverage for whole-program emission across required modules (foreign calls, class method slots, module registration). |
| tests/aot/CMakeLists.txt | Adds watchdog to AOT suites list. |
| tests/aot/_standalone_foreign_call_fixture.das | New fixture for foreign-call emission test. |
| tests/aot/_standalone_dep_call.das | New required module fixture providing foreign-used functions/methods. |
| tests/.das_test | Gates watchdog tests off under --use-aot to avoid linking non-AOT modules. |
| src/builtin/module_builtin_fio.cpp | Adds long-lived process implementation and bindings (spawn_process, drain/poll/wait, signals, pid/alive, close, sentinel). |
| skills/internal/tests_in_repo.md | Updates guidance on options no_aot and why required non-AOT modules still break test_aot. |
| skills/internal/aot_testing.md | Updates AOT testing guidance consistent with new watchdog precedent. |
| skills/daspkg.md | Documents release_include_tool("watchdog"). |
| skills/daslang/references/files-and-paths.md | Documents long-lived with_process / process_* API usage and semantics. |
| skills/cpp_integration.md | Updates standalone-context integration docs to reflect whole-program emission behavior. |
| plans/dasweb_backend.md | Updates plan references from Python watchdog to static watchdog executable. |
| plans/dasllama_io_site.md | Updates plan references from Python watchdog to watchdog.das. |
| modules/dasLLAMA/REVIEW_EXCHANGE.md | Removes watchdog-dialog requirement from exchange consent event guidance. |
| modules/dasLLAMA/ENVIRONMENT.md | Updates wording to remove watchdog-specific behavior claim. |
| modules/dasLLAMA/dasllama/dasllama_exchange.das | Removes watchdog-dialog wording coupling and updates consent-surface comments. |
| modules/dasLLAMA/dasllama/dasllama_env.das | Updates docs for DASLLAMA_EXCHANGE_ACCEPT to avoid watchdog-specific claims. |
| modules/dasLLAMA/ARCHITECTURE_ENGINE.md | Updates consent-surface description (control page only). |
| install/CLAUDE.md | Updates utils/watchdog entry to new invocation modes. |
| include/daScript/simulate/aot_builtin_fio.h | Adds AOT builtin declarations for new process APIs. |
| examples/games/latchpoint/start-server.sh | Switches example launch from Python watchdog to bin/watchdog. |
| examples/games/latchpoint/start-server.ps1 | Switches example launch from Python watchdog to watchdog.exe. |
| doc/source/stdlib/handmade/Variable-fio-process_running.rst | Documents new process_running sentinel. |
| doc/source/stdlib/handmade/function-fio-spawn_process-0x5eca268e4ba0b640.rst | Documents spawn_process. |
| doc/source/stdlib/handmade/function-fio-process_wait-0xe5ddee492688d8dc.rst | Documents process_wait. |
| doc/source/stdlib/handmade/function-fio-process_terminate-0x1227a543288285f0.rst | Documents process_terminate. |
| doc/source/stdlib/handmade/function-fio-process_poll-0x21f24a39d497e02e.rst | Documents process_poll. |
| doc/source/stdlib/handmade/function-fio-process_pid-0x349c5646dfa72325.rst | Documents process_pid. |
| doc/source/stdlib/handmade/function-fio-process_kill-0x430dd55ae318dbeb.rst | Documents process_kill. |
| doc/source/stdlib/handmade/function-fio-process_drain-0x4dd6d23e716a3381.rst | Documents process_drain. |
| doc/source/stdlib/handmade/function-fio-process_alive-0x90274ac9d5662c36.rst | Documents process_alive. |
| doc/source/stdlib/handmade/function-fio-close_process-0xf82966f695d14fec.rst | Documents close_process. |
| doc/source/stdlib/handmade/annotation-fio-SubProcess.rst | Documents SubProcess annotation. |
| doc/source/reference/utils/watchdog.rst | Updates reference docs for the watchdog tool. |
| doc/reflections/das2rst.das | Adds new process APIs to fio doc grouping. |
| daslib/REVIEW.md | Broadens emitter-review guidance for struct/enum name emission paths. |
| daslib/fio.das | Adds process typedef + with_process convenience wrappers. |
| daslib/daspkg.das | Adds tools to ReleaseSpec and exposes release_include_tool. |
| daslib/ARCHITECTURE_EMIT.md | Updates architecture notes to reflect whole-program single-TU emission. |
| daslib/aot_standalone.das | Implements whole-program emission across modules (foreign used functions + globals init temps). |
| daslib/aot_cpp.das | Adds foreignUsedFunctions and expands registration/used-module discovery for standalone whole-program emission. |
| CMakeLists.txt | Updates install to ship watchdog .das sources (exe installed from utils/CMakeLists). |
| CLAUDE.md | Adds a repo-level note about exit(N) not setting CLI exit codes. |
| ci/test_check_shipped_skills.py | Updates shipped-skill gate test string away from Python watchdog invocation. |
| CHANGELIST.md | Adds changelist entries describing watchdog migration, fio process API, and standalone emission behavior. |
| .github/workflows/extended_checks.yml | Removes Python watchdog tray/consent CI step (files removed). |
Review details
- Files reviewed: 78/78 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🔵 Needs a closer look
The new fio process implementation has a couple of concrete correctness/contract issues (stored as PR comments) that should be addressed before approving.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
src/builtin/module_builtin_fio.cpp:1884
- On POSIX, the pipe is made non-blocking via
fcntl(F_SETFL, O_NONBLOCK), which overwrites (rather than augments) the existing file status flags. PreferF_GETFLthenF_SETFL(flags | O_NONBLOCK)(and handleF_GETFLfailure) to avoid unintentionally clearing other flags.
src/builtin/module_builtin_fio.cpp:2029 - On Windows,
close_processonly closes handles. IfAssignProcessToJobObjectfailed earlier (sohJob==NULL) and the child is still running, this leaves the child alive even though the API/docs describe kill-on-close semantics. Consider a best-effortTerminateProcessfallback (whenhJobis null and the process is still active) before closing the process handle.
- Files reviewed: 78/78 changed files
- Comments generated: 0 new
- Review effort level: Lite
…ch the daslib set already emits since the file moved (two objects carried impl_aot_f16_cvt and test_aot did not link), and master's test_tune_shells.das registers so the AOT sweep links it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… points environ at it before execvp (the eastl build poisons putenv), the drain accumulator is a daslang string so it matches under eastl, CreateProcessA gets a writable command-line buffer, and the running sentinel's wording admits the one Windows collision Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Exception-safety and config-validation issues were found that can leak/strand supervised processes or crash the watchdog on Windows rather than failing gracefully.
Review details
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
daslib/fio.das:702
with_processdoes not guaranteeclose_processruns ifblkpanics/throws: the call happens afterinvoke, so an exception path can leak the handle and leave the child running/zombie. Since this helper is advertised as the safe RAII wrapper, it should close in afinallyblock.
utils/watchdog/watchdog.das:115server_argsis explicitly supported as awatchdog.jsonkey, but it is currently excluded from all type validation inconfig_to_argsand then silently ignored inconfig_server_argswhen it is not an array. That makes a mis-typedserver_args(e.g. a string) fail silently even though the config loader is otherwise strict about schema mistakes.
utils/watchdog/watchdog.das:896spawn_processthrows on Windows whenCreateProcessfails (seebuiltin_spawn_process), butSupervisor.spawn()only checks for a null handle. That means a bad--program/--daslang(or a missing executable) can crash the watchdog on Windows instead of producing aspawn_failedevent and a controlled exit.
- Files reviewed: 78/78 changed files
- Comments generated: 0 new
- Review effort level: Lite
…mode arms skip where daslang cannot start the JIT - on the Windows lane without LLVM.dll the -jit child died at startup, the supervisor restarted it every half second, and run() waited four hours; the discovery arm compares paths with the separators Windows joins with Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
utils/watchdog/watchdog.das currently treats spawn_process failure as returning null, but the builtin throws a fatal error, so spawn failures won’t be handled as intended and can abort the watchdog.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
utils/watchdog/main.das:54
- After deleting
g_supervisor, the global pointer is left dangling. If the host ever callsresult()twice (or mistakenly callstick()afterresult()), this becomes a use-after-free. Clearing it tonullafterdeletemakes the API safer at essentially no cost.
utils/watchdog/watchdog.das:896 spawn_processfailures currently can't be handled by thechild == nullbranch: the builtin callsContext::throw_error_at(...)on error (fatal panic) and never returnsnull. As written, a spawn failure will abort the watchdog instead of emittingspawn_failedand returningfalse. Wrap the call intry { ... } recover { ... }(or remove the null-check and treat spawn failure as fatal) so the control flow matches the intended non-fatal supervisor behavior.
- Files reviewed: 78/78 changed files
- Comments generated: 0 new
- Review effort level: Lite
Behavior change: the release bundles no longer carry
watchdog.py- a deploy that launchedpython watchdog.pylaunches thewatchdogexecutable at the bundle root instead, and a checked-inwatchdog.jsonmay carry only keys the flag table has.Why. Every release bundle needed Python for its supervisor, and a
-ctxstandalone context could not host the supervisor because it emitted only the entry module's functions.What changes.
fiogains a long-lived child process:spawn_process/process_drain/process_poll/process_wait/process_terminate/process_kill/process_pid/process_alive/close_process, theprocess_runningsentinel, andwith_process- a handle a supervisor drives on its own clock, with a non-blocking line drain,KEY=VALUEenvironment overrides, a working directory, and tree-wide signals.-ctx) emitter emits every used function of every module into its one translation unit - foreign functions, class methods through their slots, and the C++ modules their externs reach - with one copy per shared generic instance.utils/watchdog/watchdog.dasis the supervisor: config andwatchdog.jsonlayering, layout discovery, the exit 0 / 3 / 4 contract, ranked startup stages, a JSON-lines log with rotation, crash bundles, WER, notifications, and a stop ladder (request, terminate, kill, give up).bin/watchdogis that supervisor as a static executable (libDaScript and dasHV linked as archives, no compiler, no shared module, no lock on the files a deploy replaces);daslang utils/watchdog/main.dasruns the same code under the interpreter.release_include_tool("watchdog"): a manifest names a built tool once and daspkg resolvesbin/<name>orbin/Release/<name>.exeto the bundle root. The four manifests use it; the deploy scripts launch the binary.watchdog.py, its three Python tests and their CI step are gone; the exchange consent dialog, the sidecar tray rails and the tray icon were cut, not ported.Observable behavior.
python watchdog.py->./watchdog(.\watchdog.exe), besidewatchdog.jsonas before.-ctxprogram calling a required das module's function -> a link error or a table miss -> runs.exit(N)in a supervised das child -> the parent read 1 -> unchanged, now documented: a code comes fromdef main() : int.watchdog.json-> refused start, as before; the checked-in dasllama-server config no longer carries one.bin/watchdog --cwd utils/dasllama-serverfrom a source tree -> refused ("nothing to supervise") -> supervises: discovery also finds the daslang beside the watchdog itself.test_aotbinary on master -> two objects carryingimpl_aot_f16_cvt, no link -> links;tests/daslib/test_tune_shells.dasunder--use-aot->error[50101]-> registered and green.Where to look.
builtin_spawn_processandbuiltin_process_draininsrc/builtin/module_builtin_fio.cpp;foreignUsedFunctionsand its walks indaslib/aot_cpp.das/daslib/aot_standalone.das;Supervisor.tickandon_child_exitinutils/watchdog/watchdog.das;ship_toolsinutils/daspkg/commands.das.Validation, claims, ledger
Validation
tests/fio/test_process.das(6),tests/watchdog/test_watchdog.das(22),tests/aot/test_standalone_emit.das(24 incl. the foreign-call arm with its table-count pin),utils/daspkg/test_daspkg.das(229) - interpreted and, for the first two, under JIT;watchdog_smoke(small lane) runsbin/watchdogsupervising a daslang child through a crash and a clean exit;standalone_full_runtimeand the fourstandalone_ctx/standalone_modulesctests pass on the changed emitter;test_aot_subsetbuilds.test_aot -use-aot ... --test tests) on the rebased tip: 13232 tests, 0 failed, 0 errors, 4 skipped. It needed two fixes master owns intests/aot/CMakeLists.txt: the metal module list still addeddaslib/f16_cvt.dasafter the file moved into the daslib set (duplicate symbols, no link), and master's newtests/daslib/test_tune_shells.daswas not registered (error[50101]under--use-aot).bin/watchdogsupervisedutils/dasllama-serverwith a real model on macOS: ready in 31 s, akill -9of the child became a crash bundle, a restart and arecoveredevent in 7 s, and Ctrl-C became a/shutdownPOST, a clean child exit and exit 0.daspkg release --root utils/internal/dasweb-builddon macOS shippedwatchdoginto the.app'sContents/MacOSbesidewatchdog.jsonwith its exec bit intact.build_eastl) is the proof for the eastl-string arm ofspawn_process/process_drainand theenviron-composed child environment; no local EASTL build.PeekNamedPipe,CreateProcessfailure, env block) and of the watchdog (WER viareg.exe, the PowerShell balloon) -tests/fio/test_process.dasruns on every CI platform,tests/.das_testdoes not gatefio.tests/watchdogis gated off under--use-aotintests/.das_test:options no_aotcovers the file's own functions, but thewatchdogmodule it requires is outside the AOT set and fails the link.tests/watchdog, swept per PR.utils/daspkg/test_daspkg.dasjoinsrun_utils_tests; its unit suite ran in no CI row before.Claims - stated, not tested
kill_requested,child_unkillable): a das child cannot ignore SIGKILL, so no fixture reaches them; a break would show as a supervisor that never ends after--stop-timeout+ 20 s.process_kill/TerminateJobObject(9)andclose_processon Windows: read only.PackageReleaseInfo->cmd_release->ship_toolschain is proven by the macOS release above, not by a unit test;ship_toolsitself is.Not done
--helpon the static exe printsUsage: daslang watchdog -- [flags]- clargs' help header assumes the interpreter host.memoryevents, Windows-only in the Python) are not ported.utils/watchdog/REVIEW.dasgate that theemit()event names and theStageRulenames equal the README's lists and everyWatchdogConfigfield carries@clarg_doc; amodules/dasLLAMA/REVIEW.dascheck thatEXCHANGE_CONSENT_NOTICEequals the fixture'sconsent_notice.modules/dasLLAMA/REVIEW.md's doc-staleness rule from "call, flag, or default" to "anything the change made untrue"; restateinclude/daScript/simulate/REVIEW.md's ABI-sweep trigger as the property.