@@ -236,7 +236,15 @@ def create_harness(self, directory, pins):
236236 printf 'PG_VERSION = 18.4\\ nPG_VERSION_NUM = 180004\\ n' > "$checkout/Makefile"
237237 fi
238238 if [[ "${STUB_BLOCK_STARTED:-}" != "" && "$(basename "$checkout")" == "previous" ]]; then
239+ if [[ "${STUB_BLOCK_PID:-}" != "" ]]; then
240+ printf '%s\n ' "$$" > "$STUB_BLOCK_PID"
241+ fi
239242 touch "$STUB_BLOCK_STARTED"
243+ if [[ "${STUB_BLOCK_FOREVER:-}" != "" ]]; then
244+ while true; do
245+ sleep 60
246+ done
247+ fi
240248 while [[ ! -e "$STUB_BLOCK_RELEASE" ]]; do
241249 sleep 0.02
242250 done
@@ -335,6 +343,19 @@ def create_lock(self, cache, owner=None):
335343 (lock_dir / "owner" ).write_text (owner , encoding = "utf-8" )
336344 return lock_dir
337345
346+ def kill_process_safely (self , pid ):
347+ try :
348+ process_group = os .getpgid (pid )
349+ except ProcessLookupError :
350+ return
351+ try :
352+ if process_group == pid :
353+ os .killpg (process_group , signal .SIGKILL )
354+ else :
355+ os .kill (pid , signal .SIGKILL )
356+ except ProcessLookupError :
357+ pass
358+
338359 def test_bad_download_is_not_published_and_poisoned_cache_recovers (self ):
339360 good_content = b"good archive"
340361 expected_hash = hashlib .sha256 (good_content ).hexdigest ()
@@ -484,9 +505,10 @@ def test_terminated_fetch_releases_lock_without_continuing(self):
484505 directory , valid_pins ()
485506 )
486507 started = root / "started"
487- release = root / "release "
508+ child_pid_path = root / "child-pid "
488509 environment ["STUB_BLOCK_STARTED" ] = str (started )
489- environment ["STUB_BLOCK_RELEASE" ] = str (release )
510+ environment ["STUB_BLOCK_PID" ] = str (child_pid_path )
511+ environment ["STUB_BLOCK_FOREVER" ] = "1"
490512
491513 process = subprocess .Popen (
492514 [str (FETCH_SCRIPT )],
@@ -495,24 +517,42 @@ def test_terminated_fetch_releases_lock_without_continuing(self):
495517 text = True ,
496518 stdout = subprocess .PIPE ,
497519 stderr = subprocess .PIPE ,
520+ start_new_session = True ,
498521 )
499- deadline = time .monotonic () + 5
500- while not started .exists () and time .monotonic () < deadline :
501- time .sleep (0.02 )
502- self .assertTrue (started .exists (), "fetch did not reach locked operation" )
503-
504- os .kill (process .pid , signal .SIGTERM )
505- release .touch ()
506- process .communicate (timeout = 5 )
507-
508- self .assertNotEqual (process .returncode , 0 )
509- self .assertFalse ((cache / ".pg_compat.lock" ).exists ())
510-
511- retry_environment = environment .copy ()
512- retry_environment .pop ("STUB_BLOCK_STARTED" )
513- retry_environment .pop ("STUB_BLOCK_RELEASE" )
514- retry = self .run_fetch (retry_environment )
515- self .assertEqual (retry .returncode , 0 , retry .stderr )
522+ child_pid = None
523+ try :
524+ deadline = time .monotonic () + 5
525+ while (
526+ (not started .exists () or not child_pid_path .exists ())
527+ and time .monotonic () < deadline
528+ ):
529+ time .sleep (0.02 )
530+ self .assertTrue (
531+ started .exists (), "fetch did not reach locked operation"
532+ )
533+ self .assertTrue (
534+ child_pid_path .exists (), "blocked child did not record its PID"
535+ )
536+ child_pid = int (
537+ child_pid_path .read_text (encoding = "utf-8" ).strip ()
538+ )
539+
540+ os .kill (process .pid , signal .SIGTERM )
541+ try :
542+ process .communicate (timeout = 2 )
543+ except subprocess .TimeoutExpired :
544+ self .fail ("terminated fetch did not exit promptly" )
545+
546+ self .assertEqual (process .returncode , 143 )
547+ with self .assertRaises (ProcessLookupError ):
548+ os .kill (child_pid , 0 )
549+ self .assertFalse ((cache / ".pg_compat.lock" ).exists ())
550+ finally :
551+ if process .poll () is None :
552+ self .kill_process_safely (process .pid )
553+ process .communicate (timeout = 5 )
554+ if child_pid is not None :
555+ self .kill_process_safely (child_pid )
516556
517557
518558if __name__ == "__main__" :
0 commit comments