819e88eff38d
signal the build's process group by id, since koh's own guard skips an exited command, and hold it with a test that leaves a child behind (3.0.81)
- time
- 2026-09-27 10:24 UTC · 2 weeks ago
- machine
- Junes-MacBook-Pro.local
- agent
- deepseek/deepseek-flash
- session
- koh 3 phases 6-8
- parent
- 818acd921e18
↓ koh-819e88ef.tar.gzatlas at this save
files changed4 files
VERSION+1 −1src/build/build.v+11 −5src/build/build_test.v+19 −0v.mod+1 −1src/build/build.v+11 −5
@@ -7,6 +7,8 @@77 88 module build99 10+#include <signal.h>11+1012 import os1113 import time1214 @@ -321,15 +323,19 @@321323 time.sleep(200 * time.millisecond)322324 }323325 timed_out := child.is_alive()326+ // Whatever the command left running goes with it: a background child could327+ // still be writing the artifact after it was hashed, and a hung build must328+ // not hold a release. The group is signalled by id — the command leads its329+ // own group — because `signal_pgkill` returns early once the process has330+ // exited. Nothing is signalled before the command started, when the id is 0331+ // and the group would be koh's own.332+ if child.pid > 0 {333+ C.kill(-child.pid, C.SIGKILL)334+ }324335 if timed_out {325336 // Kill first: waiting on a hung build waits forever.326- child.signal_pgkill()327337 child.wait()328338 }329- // Whatever the command left running goes with it: a background child could330- // still be writing the artifact after it was hashed. Its own process group331- // again, so a stray child dies and nothing else does.332- child.signal_pgkill()333339 stdout := os.read_file(stdout_path) or { '' }334340 stderr := os.read_file(stderr_path) or { '' }335341 return Run{src/build/build_test.v+19 −0
@@ -304,3 +304,22 @@304304 assert err.msg().contains('produced an empty')305305 }306306 }307+308+// A command that leaves something running in the background must not outlive309+// the build: it could still be writing the artifact after it was hashed.310+fn test_build_one_takes_background_children_with_it() {311+ root := scratch('build_orphan')!312+ defer {313+ os.rmdir_all(root) or {}314+ }315+ write(root, 'koh.build', "command = mkdir -p out && printf early > out/demo && { sh -c 'sleep 2; printf late > out/demo' & }316+output = out/demo317+platforms = macos-aarch64318+")!319+ config := read_config(root)!320+ built := build_one(root, config, 'macos-aarch64', '1.2.3')!321+ assert built.size == 5322+ // Give the background child its two seconds: it must be gone by now.323+ time.sleep(3 * time.second)324+ assert os.read_file(os.join_path(root, 'out/demo'))! == 'early'325+}v.mod+1 −1
@@ -1,6 +1,6 @@11 Module {22 name: 'koh3'3- version: '3.0.80'3+ version: '3.0.81'44 description: 'One SQLite file per repo, one line of history.'55 license: 'MIT'66 }