@@ -559,13 +559,13 @@ def test_process_stream_error_signal():
559559 pool .input_queue = MagicMock ()
560560 pool .output_queue = MagicMock ()
561561 pool .output_queue .get_nowait .return_value = QueueSignals .error
562- pool .join_or_terminate = MagicMock ()
563- pool .check_worker_health = MagicMock ()
562+ pool .join_or_terminate = MagicMock () # type: ignore[assignment]
563+ pool .check_worker_health = MagicMock () # type: ignore[assignment]
564564
565565 with pytest .raises (RuntimeError , match = "Thread unexpectedly terminated" ):
566566 list (pool ._process_stream ([1 ]))
567567
568- pool .join_or_terminate .assert_called_once ()
568+ pool .join_or_terminate .assert_called_once () # type: ignore[attr-defined]
569569
570570
571571def test_join_or_terminate_mixed_states ():
@@ -586,16 +586,16 @@ def test_join_or_terminate_mixed_states():
586586
587587 pool .processes = [p1 , p2 , p3 ]
588588
589- pool .join_or_terminate (timeout = 0. 1 )
589+ pool .join_or_terminate (timeout = 1 )
590590
591591 # P1 should have been joined but not terminated
592- p1 .join .assert_called_once_with (timeout = 0. 1 )
592+ p1 .join .assert_called_once_with (timeout = 1 )
593593 p1 .terminate .assert_not_called ()
594594
595595 # P2 and P3 should have been joined AND terminated
596- p2 .join .assert_called_once_with (timeout = 0. 1 )
596+ p2 .join .assert_called_once_with (timeout = 1 )
597597 p2 .terminate .assert_called_once ()
598- p3 .join .assert_called_once_with (timeout = 0. 1 )
598+ p3 .join .assert_called_once_with (timeout = 1 )
599599 p3 .terminate .assert_called_once ()
600600
601601 # Processes list should be cleared
@@ -699,3 +699,65 @@ def test_semi_ordered_map_start_failure():
699699 # queues should be None or handled safely
700700 assert pool .input_queue is None
701701 assert pool .output_queue is None
702+ def test_check_worker_health_healthy ():
703+ """Test check_worker_health does not raise when all processes are alive or exited cleanly."""
704+ pool = ParallelWorkerPool (worker = SquareWorker , config = PoolConfig (num_workers = 2 ))
705+
706+ # Process 1: Alive
707+ p1 = MagicMock ()
708+ p1 .is_alive .return_value = True
709+ p1 .exitcode = None
710+
711+ # Process 2: Exited cleanly
712+ p2 = MagicMock ()
713+ p2 .is_alive .return_value = False
714+ p2 .exitcode = 0
715+
716+ pool .processes = [p1 , p2 ]
717+
718+ # Should not raise
719+ pool .check_worker_health ()
720+ assert pool .emergency_shutdown is False
721+
722+
723+ def test_check_worker_health_unhealthy ():
724+ """Test check_worker_health raises RuntimeError when a process terminates unexpectedly."""
725+ pool = ParallelWorkerPool (worker = SquareWorker , config = PoolConfig (num_workers = 1 ))
726+
727+ p1 = MagicMock ()
728+ p1 .is_alive .return_value = False
729+ p1 .exitcode = 1
730+ p1 .pid = 1234
731+
732+ pool .processes = [p1 ]
733+ pool .join_or_terminate = MagicMock () # type: ignore[assignment]
734+
735+ with pytest .raises (RuntimeError , match = "Worker PID: 1234 terminated unexpectedly with code 1" ):
736+ pool .check_worker_health ()
737+
738+ assert pool .emergency_shutdown is True
739+ pool .join_or_terminate .assert_called_once () # type: ignore[attr-defined]
740+
741+
742+ def test_check_worker_health_called_during_processing ():
743+ """Test that check_worker_health is called during the processing loop."""
744+ pool = ParallelWorkerPool (worker = SquareWorker , config = PoolConfig (num_workers = 1 ))
745+
746+ # Mock necessary parts to avoid real multiprocessing
747+ pool .start = MagicMock () # type: ignore[assignment]
748+ pool .join = MagicMock () # type: ignore[assignment]
749+ pool .input_queue = MagicMock () # type: ignore[assignment]
750+ pool .output_queue = MagicMock () # type: ignore[assignment]
751+
752+ # Simulate one item being processed
753+ # First call to get_nowait returns None, second call returns item
754+ pool .output_queue .get_nowait .side_effect = [None , (0 , 100 )] # type: ignore[attr-defined]
755+ # Final get call for the remaining items
756+ pool .output_queue .get .return_value = (0 , 100 ) # type: ignore[attr-defined]
757+
758+ with patch .object (ParallelWorkerPool , "check_worker_health" ) as mock_check_health :
759+ # We need to exhaust the generator
760+ list (pool .semi_ordered_map ([10 ]))
761+
762+ # It should be called at least once
763+ assert mock_check_health .called
0 commit comments