Skip to content

Commit 28ceb5e

Browse files
committed
Added logic to retry database refreshes and file path validation to avoid race conditions when transferring/registering items
1 parent 639a8fd commit 28ceb5e

File tree

4 files changed

+245
-72
lines changed

4 files changed

+245
-72
lines changed

src/murfey/server/api/clem.py

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import re
4+
import time
45
import traceback
56
from ast import literal_eval
67
from importlib.metadata import EntryPoint # type hinting only
@@ -94,7 +95,14 @@ def validate_and_sanitise(
9495
raise ValueError(f"{file} points to a directory that is not permitted")
9596

9697
# Check that it's a file, not a directory
97-
if full_path.is_file() is False:
98+
# Make a couple of attempts to rule out race condition
99+
attempts = 0
100+
while attempts < 50:
101+
if full_path.is_file() is True:
102+
break
103+
attempts += 1
104+
time.sleep(0.1)
105+
else:
98106
raise ValueError(f"{file} is not a file")
99107

100108
# Check that it is of a permitted file type
@@ -184,7 +192,23 @@ def get_db_entry(
184192
)
185193
db.add(db_entry)
186194
db.commit()
187-
db.refresh(db_entry)
195+
196+
# Make multiple attempts to refresh data in case of race condition
197+
attempts = 0
198+
while attempts < 50:
199+
try:
200+
db.refresh(db_entry)
201+
break
202+
except Exception:
203+
pass
204+
attempts += 1
205+
time.sleep(0.1)
206+
else:
207+
raise RuntimeError(
208+
"Maximum number of attempts reached while trying to refresh database "
209+
f"entry for {str(file_path if file_path else series_name)!r}"
210+
)
211+
188212
except Exception:
189213
raise Exception
190214

@@ -215,7 +239,10 @@ def register_lif_file(
215239
file_path=lif_file,
216240
)
217241
except Exception:
218-
logger.error(traceback.format_exc())
242+
logger.error(
243+
f"Exception encountered while registering LIF file {str(lif_file)!r}: \n"
244+
f"{traceback.format_exc()}"
245+
)
219246
return False
220247

221248
# Add metadata information if provided
@@ -224,7 +251,11 @@ def register_lif_file(
224251
master_metadata = validate_and_sanitise(master_metadata, session_id, db)
225252
clem_lif_file.master_metadata = str(master_metadata)
226253
except Exception:
227-
logger.warning(traceback.format_exc())
254+
logger.warning(
255+
"Unable to add master metadata information to database entry for "
256+
f"LIF file {str(lif_file)!r}: \n"
257+
f"{traceback.format_exc()}"
258+
)
228259

229260
# Register child metadata if provided
230261
for metadata in child_metadata:
@@ -238,7 +269,11 @@ def register_lif_file(
238269
# Append to database entry
239270
clem_lif_file.child_metadata.append(metadata_db_entry)
240271
except Exception:
241-
logger.warning(traceback.format_exc())
272+
logger.warning(
273+
"Unable to add child metadata information to database entry for "
274+
f"LIF file {str(lif_file)!r}: \n"
275+
f"{traceback.format_exc()}"
276+
)
242277
continue
243278

244279
# Register child image series if provided
@@ -253,7 +288,11 @@ def register_lif_file(
253288
# Append to database entry
254289
clem_lif_file.child_series.append(series_db_entry)
255290
except Exception:
256-
logger.warning(traceback.format_exc())
291+
logger.warning(
292+
"Unable to add child series information to database entry for "
293+
f"LIF file {str(lif_file)!r}: \n"
294+
f"{traceback.format_exc()}"
295+
)
257296
continue
258297

259298
# Register child image stacks if provided
@@ -268,7 +307,11 @@ def register_lif_file(
268307
# Append to database entry
269308
clem_lif_file.child_stacks.append(stack_db_entry)
270309
except Exception:
271-
logger.warning(traceback.format_exc())
310+
logger.warning(
311+
"Unable to add child image stack information to database entry for "
312+
f"LIF file {str(lif_file)!r}: \n"
313+
f"{traceback.format_exc()}"
314+
)
272315
continue
273316

274317
# Commit to database
@@ -296,7 +339,10 @@ def register_tiff_file(
296339
file_path=tiff_file,
297340
)
298341
except Exception:
299-
logger.error(traceback.format_exc())
342+
logger.error(
343+
f"Exception encountered while registering TIFF file {str(tiff_file)!r}: \n"
344+
f"{traceback.format_exc()}"
345+
)
300346
return False
301347

302348
# Add metadata if provided
@@ -311,7 +357,10 @@ def register_tiff_file(
311357
# Link database entries
312358
clem_tiff_file.associated_metadata = metadata_db_entry
313359
except Exception:
314-
logger.warning(traceback.format_exc())
360+
logger.warning(
361+
f"Unable to register metadata for TIFF file {str(tiff_file)!r}: \n"
362+
f"{traceback.format_exc()}"
363+
)
315364

316365
# Add series information if provided
317366
if associated_series is not None:
@@ -325,7 +374,11 @@ def register_tiff_file(
325374
# Link database entries
326375
clem_tiff_file.child_series = series_db_entry
327376
except Exception:
328-
logger.warning(traceback.format_exc())
377+
logger.warning(
378+
"Unable to register series information for TIFF file "
379+
f"{str(tiff_file)!r}: \n"
380+
f"{traceback.format_exc()}"
381+
)
329382

330383
# Add image stack information if provided
331384
if associated_stack is not None:
@@ -339,7 +392,11 @@ def register_tiff_file(
339392
# Link database entries
340393
clem_tiff_file.child_stack = stack_db_entry
341394
except Exception:
342-
logger.warning(traceback.format_exc())
395+
logger.warning(
396+
f"Unable to register image stack {str(associated_stack)!r} "
397+
f"in database for TIFF file {str(tiff_file)!r}: \n"
398+
f"{traceback.format_exc()}"
399+
)
343400

344401
# Commit to database
345402
db.add(clem_tiff_file)

src/murfey/workflows/clem/__init__.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import logging
44
import re
5+
import time
56
from pathlib import Path
67
from typing import Optional, Type, Union
78

@@ -90,7 +91,14 @@ def _validate_and_sanitise(
9091
raise ValueError(f"{file} points to a directory that is not permitted")
9192

9293
# Check that it's a file, not a directory
93-
if full_path.is_file() is False:
94+
# Make a couple of attempts to rule out race condition
95+
attempts = 0
96+
while attempts < 50:
97+
if full_path.is_file() is True:
98+
break
99+
attempts += 1
100+
time.sleep(0.1)
101+
else:
94102
raise ValueError(f"{file} is not a file")
95103

96104
# Check that it is of a permitted file type
@@ -180,7 +188,22 @@ def get_db_entry(
180188
)
181189
db.add(db_entry)
182190
db.commit()
183-
db.refresh(db_entry)
191+
192+
# Make multiple attempts data retrieval attempts in case of race condition
193+
attempts = 0
194+
while attempts < 50:
195+
try:
196+
db.refresh(db_entry)
197+
break
198+
except Exception:
199+
pass
200+
attempts += 1
201+
time.sleep(0.1)
202+
else:
203+
raise RuntimeError(
204+
"Maximum number of attempts reached while trying to refresh database "
205+
f"entry for {str(file_path if file_path else series_name)!r}"
206+
)
184207
except Exception:
185208
raise Exception
186209

src/murfey/workflows/clem/register_align_and_merge_results.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
import logging
5+
import time
56
import traceback
67
from ast import literal_eval
78
from pathlib import Path
@@ -60,29 +61,22 @@ def register_align_and_merge_result(
6061
)
6162

6263
# Validate message and try and load results
63-
if isinstance(message["result"], str):
64-
try:
64+
try:
65+
if isinstance(message["result"], str):
6566
json_obj: dict = json.loads(message["result"])
6667
result = AlignAndMergeResult(**json_obj)
67-
except Exception:
68-
logger.error(traceback.format_exc())
69-
logger.error(
70-
"Exception encountered when parsing align-and-merge processing result"
71-
)
72-
return False
73-
elif isinstance(message["result"], dict):
74-
try:
68+
elif isinstance(message["result"], dict):
7569
result = AlignAndMergeResult(**message["result"])
76-
except Exception:
77-
logger.error(traceback.format_exc())
70+
else:
7871
logger.error(
79-
"Exception encountered when parsing align-and-merge processing result"
72+
"Invalid type for align-and-merge processing result: "
73+
f"{type(message['result'])}"
8074
)
8175
return False
82-
else:
76+
except Exception:
8377
logger.error(
84-
"Invalid type for align-and-merge processing result: "
85-
f"{type(message['result'])}"
78+
"Exception encountered when parsing align-and-merge processing result: \n"
79+
f"{traceback.format_exc()}"
8680
)
8781
return False
8882

@@ -100,18 +94,33 @@ def register_align_and_merge_result(
10094
clem_img_series.composite_created = True
10195
murfey_db.add(clem_img_series)
10296
murfey_db.commit()
103-
murfey_db.refresh(clem_img_series)
97+
98+
# Make multiple attempts to refresh data in case of race condition
99+
attempts = 0
100+
while attempts < 50:
101+
try:
102+
murfey_db.refresh(clem_img_series)
103+
break
104+
except Exception:
105+
pass
106+
attempts += 1
107+
time.sleep(0.1)
108+
else:
109+
raise RuntimeError(
110+
"Maximum number of attempts reached while trying to refresh database "
111+
f"entry for {result.series_name!r}"
112+
)
104113

105114
logger.info(
106115
"Align-and-merge processing result registered for "
107116
f"{result.series_name!r} series"
108117
)
109118

110119
except Exception:
111-
logger.error(traceback.format_exc())
112120
logger.error(
113121
"Exception encountered when registering LIF preprocessing result for "
114-
f"{result.series_name!r} {result.channel!r} image stack"
122+
f"{result.series_name!r} {result.channel!r} image stack: \n"
123+
f"{traceback.format_exc()}"
115124
)
116125
return False
117126

0 commit comments

Comments
 (0)