@@ -76,42 +76,59 @@ def test_prevent_racing_condition(self):
7676 """Test that the `JobsList` prevents racing condition when updating job info.
7777
7878 This test simulates a race condition where:
79- 1. Job 'job1' requests an update
80- 2. During the scheduler query, a new job 'job2' also requests an update
81- 3. JobList must only update about 'job1'
82- 4. 'job2' future should be kept pending for the next update cycle
79+ 1. Job job_id_a requests an update
80+ 2. During the scheduler query, a new job job_id_b also requests an update
81+ 3. JobList must only update about job_id_a
82+ 4. job_id_b future should be kept pending for the next update cycle
8383 """
8484 from unittest .mock import patch
8585
8686 from aiida .schedulers .datastructures import JobInfo , JobState
8787
8888 jobs_list = self .jobs_list
8989
90- mock_job_info = JobInfo ()
91- mock_job_info .job_id = 'job1'
92- mock_job_info .job_state = JobState .RUNNING
90+ mock_job_info_a = JobInfo ()
91+ job_id_a = 'A'
92+ mock_job_info_a .job_id = job_id_a
93+ mock_job_info_a .job_state = JobState .RUNNING
94+
95+ mock_job_info_b = JobInfo ()
96+ job_id_b = 10 # intentionally using int to test str conversion
97+ mock_job_info_b .job_id = job_id_b
9398
9499 def mock_get_jobs (** kwargs ):
95100 # Simulate the race: job2 is added to _job_update_requests while we're querying the scheduler
96- jobs_list ._job_update_requests .setdefault ('job2' , asyncio .Future ())
101+ jobs_list ._job_update_requests .setdefault (str ( job_id_b ) , asyncio .Future ())
97102
98- # Return only job1 (scheduler was queried with only job1 )
99- return {'job1' : mock_job_info }
103+ # Return only job_id_a (scheduler was queried with only job_id_a )
104+ return {job_id_a : mock_job_info_a }
100105
101- # Request update for job1
102- future1 = jobs_list ._job_update_requests .setdefault ('job1' , asyncio .Future ())
106+ # Request update for job_id_a
107+ future1 = jobs_list ._job_update_requests .setdefault (str ( job_id_a ) , asyncio .Future ())
103108
104109 # Patch the scheduler's get_jobs
105110 scheduler = self .auth_info .computer .get_scheduler ()
106111 with patch .object (scheduler .__class__ , 'get_jobs' , side_effect = mock_get_jobs ):
107112 self .loop .run_until_complete (jobs_list ._update_job_info ())
108113
109- # Verify job1 was resolved correctly
110- assert future1 .done (), 'job1 future should be resolved'
111- assert future1 .result () == mock_job_info , 'job1 should have the correct JobInfo'
114+ # Verify job_id_a was resolved correctly
115+ assert future1 .done (), 'job_id_a future should be resolved'
116+ assert future1 .result () == mock_job_info_a , 'job_id_a should have the correct JobInfo'
112117
113118 # Verify job2 was NOT resolved and it has remained in _job_update_requests for the next cycle
114- assert 'job2' in jobs_list ._job_update_requests , 'job2 should still be in update requests'
115- future2 = jobs_list ._job_update_requests ['job2' ]
116- assert not future2 .done (), 'job2 future should NOT be resolved yet (prevented racing bug)'
117- assert len (jobs_list ._job_update_requests ) == 1 , 'Only job2 should remain in update requests'
119+ assert str (job_id_b ) in jobs_list ._job_update_requests , 'job_id_b should still be in update requests'
120+ future2 = jobs_list ._job_update_requests [str (job_id_b )]
121+ assert not future2 .done (), 'job_id_b future should NOT be resolved yet (prevented racing bug)'
122+ assert len (jobs_list ._job_update_requests ) == 1 , 'Only job_id_b should remain in update requests'
123+
124+ def mock_get_jobs (** kwargs ):
125+ # Intentionally return empty dict to simulate job_id_b
126+ # not being in the scheduler anymore. Simulated as finished.
127+ return {}
128+
129+ future2 = jobs_list ._job_update_requests .get (str (job_id_b ))
130+
131+ with patch .object (scheduler .__class__ , 'get_jobs' , side_effect = mock_get_jobs ):
132+ self .loop .run_until_complete (jobs_list ._update_job_info ())
133+
134+ assert future2 .done (), 'job_id_b future should be resolved'
0 commit comments