@@ -82,7 +82,7 @@ extern "C" {
8282
8383// Starting a program. One function since openkal 0.11.
8484//
85- // ⚠️ TWO OF THE FIVE POSITIONS IN `kal_spawn' ARE REFUSED HERE, AND EACH REFUSAL
85+ // ⚠️ TWO POSITIONS IN `kal_spawn' ARE REFUSED HERE, AND EACH REFUSAL
8686// IS OLDER THAN 0.11 --- the record moved, the answers did not.
8787//
8888// `grants': this environment has no numbering a preopen could arrive under, so
@@ -93,19 +93,32 @@ extern "C" {
9393//
9494// `KAL_SPAWN_BOUND_LIFETIME': no primitive arms it from inside the started image.
9595//
96- // ⚠️⚠️ `KAL_SPAWN_OWN_JOB' IS REFUSED, AND NOT BECAUSE THIS SYSTEM CANNOT DO IT ---
97- // a job object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE is exactly the unit the
98- // flag describes, and this is the environment the idea comes from. What is
99- // missing is somewhere to keep the job: `kal_process' is one machine word and it
100- // holds the process handle, so `kal_process_terminate' would have no way to
101- // reach the job later. The other two implementations need no such storage
102- // because `getpgid(pid) == pid' recovers the fact from the kernel.
96+ // ⭐⭐ AND THE UNIT IS IMPLEMENTED HERE, WHICH AN EARLIER SHAPE OF IT WAS NOT.
10397//
104- // ⇒ Claiming it would mean a side table keyed by process handle, and a side
105- // table that is wrong under concurrency terminates the wrong tree. Refused
106- // until it can be done without one, which is a smaller lie than "kills
107- // sometimes". Clause 6.2: the position is not claimed and a caller that asks
108- // first is told.
98+ // 0.11 first spelled this as a flag: make the started program a unit, and let
99+ // `kal_process_terminate' reach the unit afterwards. That shape could not be
100+ // satisfied here. This system can FORM the unit --- a job object is exactly it ---
101+ // but it cannot RECOVER one from a process handle, and `kal_process' is one word
102+ // already holding the process. The other two implementations needed no storage
103+ // because `getpgid(pid) == pid' recovers it from the kernel; this one would have
104+ // needed a registry.
105+ //
106+ // ⚠️ Clause 7.1 states mechanically what needing a registry means: the
107+ // specification "has taken a shape borrowed from one environment, and THE SHAPE
108+ // IS AT FAULT rather than the implementation". handle.h says the same one level
109+ // down --- its array "holds generations and nothing else", and a lookup deciding
110+ // what a word referred to "would be a defect here".
111+ //
112+ // ⇒ So the shape changed rather than this file acquiring a table. The unit is now
113+ // a handle the CALLER holds, whose identity is established at the first start, and
114+ // both kinds of system perform that without remembering anything: here a job
115+ // object is created and its handle reported; where the unit is a process group
116+ // the first member's identifier is reported instead.
117+ //
118+ // ⚠️ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE IS DELIBERATELY NOT SET. It would make
119+ // `kal_process_job_close' end every member --- and closing means only releasing
120+ // where the unit is a number, so one operation would mean two things. Ending is
121+ // `kal_process_job_terminate' and nothing else is.
109122int kal_process_spawn (const kal_spawn* how,
110123 const char * path, kal_uintptr path_len,
111124 const char ** argv, const kal_uintptr* argv_lens, kal_uintptr argc,
@@ -120,6 +133,25 @@ int kal_process_spawn(const kal_spawn* how,
120133 if (how->grant_count > 0 ) return kal_err_not_supported;
121134 if (how->flags != 0 ) return kal_err_not_supported;
122135
136+ // The unit, created before its first member and reported to the caller. A
137+ // later member is assigned to the one the caller already holds.
138+ HANDLE unit = nullptr ;
139+ bool unit_is_new = false ;
140+ if (how->job ) {
141+ if (how->job ->h != 0 ) {
142+ unit = okw::unpack (how->job ->h );
143+ if (!unit) return kal_err_invalid;
144+ } else {
145+ unit = CreateJobObjectW (nullptr , nullptr );
146+ if (!unit) return okw::translate_win32 (GetLastError ());
147+ unit_is_new = true ;
148+ }
149+ }
150+ struct unit_guard {
151+ HANDLE h; bool own;
152+ ~unit_guard () { if (own && h) CloseHandle (h); }
153+ } ug{ unit, unit_is_new };
154+
123155 // The directory's own name, and the program's beneath it.
124156 // Obtained rather than kept in static storage: static storage shared
125157 // between execution contexts would make two concurrent spawns one.
@@ -214,10 +246,40 @@ int kal_process_spawn(const kal_spawn* how,
214246 envc ? block : nullptr , cwd, &startup, &info);
215247 if (!started) return okw::translate_win32 (GetLastError ());
216248 CloseHandle (info.hThread );
249+
250+ // ⚠️ ASSIGNED BEFORE THE CALLER IS TOLD ANYTHING. A program that could not be
251+ // put into the unit is not the program that was asked for --- it would outlive
252+ // a termination of the unit --- so it is ended rather than handed back.
253+ if (unit && !AssignProcessToJobObject (unit, info.hProcess )) {
254+ const DWORD why = GetLastError ();
255+ TerminateProcess (info.hProcess , 127 );
256+ CloseHandle (info.hProcess );
257+ return okw::translate_win32 (why);
258+ }
259+
260+ // The caller's word, written only now, and only for a unit this start made.
261+ if (unit_is_new) { how->job ->h = okw::pack (unit); ug.own = false ; }
262+
217263 *out = kal_process{ okw::pack (info.hProcess ) };
218264 return kal_ok;
219265}
220266
267+ // Every program in the unit. A job ends its members as one, which is the whole
268+ // reason this environment's job object is the right thing to build a unit from.
269+ int kal_process_job_terminate (kal_job j) {
270+ HANDLE h = okw::unpack (j.h );
271+ if (!h) return kal_err_invalid;
272+ if (!TerminateJobObject (h, 15 )) return okw::translate_win32 (GetLastError ());
273+ return kal_ok;
274+ }
275+
276+ // ⚠️ RELEASES AND DOES NOT END. The limit that would have ended the members on
277+ // the last close is deliberately not set --- see the note above kal_process_spawn.
278+ void kal_process_job_close (kal_job j) {
279+ HANDLE h = okw::unpack (j.h );
280+ if (h) { okw::retire (j.h ); CloseHandle (h); }
281+ }
282+
221283// A channel: a pair of streams of which one end is meant to cross a spawn.
222284//
223285// THIS ENVIRONMENT DECIDES INHERITANCE PER HANDLE AND NOT PER EXEC, which is the
@@ -305,11 +367,10 @@ void kal_process_close(kal_process p) {
305367//
306368// GRANT_DIR kal_process_spawn refuses a non-empty `grants'
307369// BOUND_LIFETIME no primitive arms it from inside the started image
308- // OWN_JOB the job exists; somewhere to keep its handle does not ---
309- // see the note above kal_process_spawn
310370kal_uintptr kal_process_props (void ) { return
311371 KAL_PROCESS_PROP_TERMINATE | KAL_PROCESS_PROP_STREAM_PASSING
312372 | KAL_PROCESS_PROP_EXIT_STATUS
313- | KAL_PROCESS_PROP_CHANNEL ; }
373+ | KAL_PROCESS_PROP_CHANNEL
374+ | KAL_PROCESS_PROP_JOB ; }
314375
315376}
0 commit comments