@@ -1262,8 +1262,10 @@ async def test_file_checkpoint_storage_delete():
12621262
12631263
12641264async def test_file_checkpoint_storage_concurrent_delete ():
1265+ """Serialize deletes when overlapping unlink calls could both report success."""
12651266 with tempfile .TemporaryDirectory () as temp_dir :
12661267 storage = FileCheckpointStorage (temp_dir )
1268+ other_storage = FileCheckpointStorage (temp_dir )
12671269 checkpoint = WorkflowCheckpoint (
12681270 workflow_name = "test-workflow" ,
12691271 graph_signature_hash = "test-hash" ,
@@ -1272,19 +1274,44 @@ async def test_file_checkpoint_storage_concurrent_delete():
12721274 await storage .save (checkpoint )
12731275
12741276 file_path = (Path (temp_dir ) / "same.json" ).resolve ()
1275- original_exists = Path .exists
1276- barrier = threading .Barrier (2 )
1277-
1278- def synchronized_exists (path : Path ) -> bool :
1279- result = original_exists (path )
1280- if path .resolve () == file_path :
1281- barrier .wait (timeout = 5 )
1282- return result
1283-
1284- with patch .object (Path , "exists" , synchronized_exists ):
1277+ original_to_thread = asyncio .to_thread
1278+ original_unlink = Path .unlink
1279+ worker_barrier = threading .Barrier (2 )
1280+ unlink_barrier = threading .Barrier (2 )
1281+ unlink_guard = threading .Lock ()
1282+ overlapping_delete_completed = False
1283+
1284+ async def synchronized_to_thread (function : Any , / , * args : Any , ** kwargs : Any ) -> Any :
1285+ def synchronized_call () -> Any :
1286+ worker_barrier .wait (timeout = 5 )
1287+ return function (* args , ** kwargs )
1288+
1289+ return await original_to_thread (synchronized_call )
1290+
1291+ def macos_style_unlink (path : Path , missing_ok : bool = False ) -> None :
1292+ nonlocal overlapping_delete_completed
1293+ if path .resolve () != file_path :
1294+ original_unlink (path , missing_ok = missing_ok )
1295+ return
1296+
1297+ try :
1298+ unlink_barrier .wait (timeout = 0.5 )
1299+ except threading .BrokenBarrierError :
1300+ original_unlink (path , missing_ok = missing_ok )
1301+ return
1302+
1303+ with unlink_guard :
1304+ if not overlapping_delete_completed :
1305+ original_unlink (path , missing_ok = missing_ok )
1306+ overlapping_delete_completed = True
1307+
1308+ with (
1309+ patch .object (asyncio , "to_thread" , synchronized_to_thread ),
1310+ patch .object (Path , "unlink" , macos_style_unlink ),
1311+ ):
12851312 results = await asyncio .gather (
12861313 storage .delete (checkpoint .checkpoint_id ),
1287- storage .delete (checkpoint .checkpoint_id ),
1314+ other_storage .delete (checkpoint .checkpoint_id ),
12881315 )
12891316
12901317 assert sorted (results ) == [False , True ]
0 commit comments