@@ -316,7 +316,7 @@ def cancel_superseded(base, headers, job, match, current):
316316 tree = urllib .parse .quote ("builds[number,building,actions[parameters[name,value]]]" , safe = "" )
317317 for build in jget (base , headers , f"/{ path } /api/json?tree={ tree } " ).get ("builds" , []):
318318 if build .get ("building" ) and superseded (build .get ("actions" ), match , current ):
319- jpost (base , headers , f"/ { path } / { build [' number' ] } /stop" )
319+ stop_build (base , headers , job , build [" number" ] )
320320 tree = urllib .parse .quote ("items[id,task[fullName],actions[parameters[name,value]]]" , safe = "" )
321321 for item in jget (base , headers , f"/queue/api/json?tree={ tree } " ).get ("items" , []):
322322 if (item .get ("task" ) or {}).get ("fullName" ) == job and superseded (item .get ("actions" ), match , current ):
@@ -330,7 +330,7 @@ def cancel_matching(base, headers, job, match):
330330 values = parameters (build .get ("actions" ))
331331 if build .get ("building" ) and all (values .get (key ) == value for key , value in match .items ()):
332332 print (f"Stopping superseded Jenkins build { job } #{ build ['number' ]} ." )
333- jpost (base , headers , f"/ { path } / { build [' number' ] } /stop" )
333+ stop_build (base , headers , job , build [" number" ] )
334334 tree = urllib .parse .quote ("items[id,task[fullName],actions[parameters[name,value]]]" , safe = "" )
335335 for item in jget (base , headers , f"/queue/api/json?tree={ tree } " ).get ("items" , []):
336336 values = parameters (item .get ("actions" ))
@@ -355,11 +355,42 @@ def start_files_job(base, headers, job, fields, files):
355355 return wait_queue (base , headers , response_headers ["Location" ], job )
356356
357357
358- def stop_build (base , headers , job , number ):
358+ def build_is_running (base , headers , job , number ):
359+ path = job_path (job )
359360 try :
360- jpost ( base , headers , f"/{ job_path ( job ) } /{ number } /stop" )
361+ return bool ( jget ( base , headers , f"/{ path } /{ number } /api/json?tree=building" ). get ( "building" ) )
361362 except CiError as exc :
362- print (f"Could not stop Jenkins build { job } #{ number } : { exc } " )
363+ print (f"Could not query Jenkins build { job } #{ number } : { exc } " )
364+ return False
365+
366+
367+ def wait_build_stopped (base , headers , job , number , timeout_seconds ):
368+ deadline = time .monotonic () + timeout_seconds
369+ while time .monotonic () < deadline :
370+ if not build_is_running (base , headers , job , number ):
371+ return True
372+ time .sleep (2 )
373+ return not build_is_running (base , headers , job , number )
374+
375+
376+ def stop_build (base , headers , job , number ):
377+ path = job_path (job )
378+ if not build_is_running (base , headers , job , number ):
379+ return True
380+ for endpoint , wait_seconds in [("stop" , 30 ), ("term" , 60 ), ("kill" , 30 )]:
381+ if not build_is_running (base , headers , job , number ):
382+ return True
383+ try :
384+ print (f"Requesting Jenkins { endpoint } for { job } #{ number } ." )
385+ jpost (base , headers , f"/{ path } /{ number } /{ endpoint } " )
386+ except CiError as exc :
387+ print (f"Could not request Jenkins { endpoint } for { job } #{ number } : { exc } " )
388+ if wait_build_stopped (base , headers , job , number , wait_seconds ):
389+ print (f"Jenkins build { job } #{ number } stopped after { endpoint } ." )
390+ return True
391+ if build_is_running (base , headers , job , number ):
392+ raise CiError (f"Jenkins build { job } #{ number } is still running after stop escalation." )
393+ return True
363394
364395
365396def redact_console (text ):
0 commit comments