okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
f835864
main
1"""Checked group termination for the erlang supervisor. 2 3Erlang runs this as its own process, so supervision keeps making progress when 4the kernel cannot (a native cell holding the GIL, a wedged interpreter), and so 5the supervisor never parses kill(1) error text or guesses an exit status. One 6argv carries the request (or `-` and one stdin line, the form run over ssh 7for a remote kernel's processes); one JSON object on stdout carries the 8verdict. 9""" 10 11from __future__import annotations 12 13import signal 14 15import asyncio 16import json 17import os 18import sys 19 20import albedo_proc 21 22TERM_MS = 250 23KILL_MS = 1000 # the supervisor's remaining budget is short: this is the last resort 24 25 26def budget (request :dict ,name :str ,default :int )-> float : 27"""Seconds for one ladder step, clamped to something a caller can wait for.""" 28value = request .get (name ,default ) 29return ( 30min (max (value ,10 ),default )/ 1000 31if isinstance (value ,int ) 32else default / 1000 33 ) 34 35 36def target (spec :object )-> albedo_proc .Group : 37"""One requested target: a process group, or one process that leads none.""" 38if not isinstance (spec ,dict ): 39raise ValueError ("target must be an object" ) 40pgid = spec .get ("pgid" ) 41operand = pgid if isinstance (pgid ,int )else spec .get ("pid" ) 42if not isinstance (operand ,int )or operand <= 1 : 43raise ValueError ("target needs a pgid or pid above 1" ) 44leader = spec .get ("leader" ) 45whole = isinstance (pgid ,int ) 46if not whole : 47try : 48whole = os .getpgid (operand )== operand 49except (ProcessLookupError ,PermissionError ): 50pass 51return albedo_proc .Group ( 52operand ,leader if isinstance (leader ,str )else None ,whole 53 ) 54 55 56def supervise (request :object )-> dict [str ,object ]: 57if not isinstance (request ,dict ): 58raise ValueError ("request must be an object" ) 59specs = request .get ("targets" ) 60if not isinstance (specs ,list ): 61raise ValueError ("request needs a target list" ) 62targets = [target (spec )for spec in specs ] 63endings = asyncio .run ( 64albedo_proc .terminate ( 65targets , 66term = budget (request ,"term_ms" ,TERM_MS ), 67kill = budget (request ,"kill_ms" ,KILL_MS ), 68 ) 69 ) 70return { 71"targets" : [ 72 {** ending .as_json (),"label" :spec .get ("label" )} 73for spec ,ending in zip (specs ,endings ) 74 ] 75 } 76 77 78def main (request :str )-> int : 79try : 80answer :dict [str ,object ]= {"ok" :True ,** supervise (json .loads (request ))} 81status = 0 82except Exception as error : 83answer = {"ok" :False ,"error" :f" { type ( error ). __name__ } : { error } " } 84status = 2 85json .dumps (answer )) 86return status 87 88 89if __name__ == "__main__" : 90# Default SIGALRM terminates even a wedged interpreter; no Python handler. 91# The owning port waits longer than this before reporting a helper timeout. 92signal .signal (signal .SIGALRM ,signal .SIG_DFL ) 93signal .alarm (3 ) 94request = sys .argv [1 ]if len (sys .argv )> 1 else "{}" 95sys .exit (main (sys .stdin .readline ()if request == "-" else request ))