Skip to content

Commit 6e2bd0e

Browse files
committed
fix typing issue
1 parent a1b1108 commit 6e2bd0e

File tree

2 files changed

+40
-23
lines changed

2 files changed

+40
-23
lines changed

src/aiida/engine/processes/calcjobs/manager.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ def __init__(self, authinfo: AuthInfo, transport_queue: 'TransportQueue', last_u
5757
self._loop = transport_queue.loop
5858
self._logger = logging.getLogger(__name__)
5959

60-
self._jobs_cache: Dict[Hashable, 'JobInfo'] = {}
61-
self._job_update_requests: Dict[Hashable, asyncio.Future] = {} # Mapping: {job_id: Future}
60+
self._jobs_cache: Dict[str, 'JobInfo'] = {}
61+
self._job_update_requests: Dict[str, asyncio.Future] = {} # Mapping: {job_id: Future}
6262
self._last_updated = last_updated
6363
self._update_handle: Optional[asyncio.TimerHandle] = None
6464
self._polling_jobs: List[str] = []
@@ -88,7 +88,7 @@ def last_updated(self) -> Optional[float]:
8888
"""
8989
return self._last_updated
9090

91-
async def _get_jobs_from_scheduler(self) -> Dict[Hashable, 'JobInfo']:
91+
async def _get_jobs_from_scheduler(self) -> Dict[str, 'JobInfo']:
9292
"""Get the current jobs list from the scheduler.
9393
9494
:return: a mapping of job ids to :py:class:`~aiida.schedulers.datastructures.JobInfo` instances
@@ -168,7 +168,7 @@ def request_job_info_update(self, authinfo: AuthInfo, job_id: Hashable) -> Itera
168168
"""
169169
self._authinfo = authinfo
170170
# Get or create the future
171-
request = self._job_update_requests.setdefault(job_id, asyncio.Future())
171+
request = self._job_update_requests.setdefault(str(job_id), asyncio.Future())
172172
assert not request.done(), 'Expected pending job info future, found in done state.'
173173

174174
try:

tests/engine/test_manager.py

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)