52
52
from .stdfsaccess import StdFsAccess # pylint: disable=unused-import
53
53
from .utils import (aslist , convert_pathsep_to_unix ,
54
54
docker_windows_path_adjust , json_dumps , onWindows ,
55
- random_outdir , windows_default_container_id )
55
+ random_outdir , windows_default_container_id ,
56
+ shared_file_lock , upgrade_lock )
56
57
if TYPE_CHECKING :
57
58
from .provenance import ProvenanceProfile # pylint: disable=unused-import
58
59
@@ -353,8 +354,9 @@ def job(self,
353
354
354
355
interesting = {"DockerRequirement" ,
355
356
"EnvVarRequirement" ,
356
- "CreateFileRequirement" ,
357
- "ShellCommandRequirement" }
357
+ "InitialWorkDirRequirement" ,
358
+ "ShellCommandRequirement" ,
359
+ "NetworkAccess" }
358
360
for rh in (self .original_requirements , self .original_hints ):
359
361
for r in reversed (rh ):
360
362
if r ["class" ] in interesting and r ["class" ] not in keydict :
@@ -369,34 +371,55 @@ def job(self,
369
371
keydictstr , cachekey )
370
372
371
373
jobcache = os .path .join (runtimeContext .cachedir , cachekey )
372
- jobcachepending = "{}.{}.pending" .format (
373
- jobcache , threading .current_thread ().ident )
374
374
375
- if os .path .isdir (jobcache ) and not os .path .isfile (jobcachepending ):
375
+ # Create a lockfile to manage cache status.
376
+ jobcachepending = "{}.status" .format (jobcache )
377
+ jobcachelock = None
378
+ jobstatus = None
379
+
380
+ # Opens the file for read/write, or creates an empty file.
381
+ jobcachelock = open (jobcachepending , "a+" )
382
+
383
+ # get the shared lock to ensure no other process is trying
384
+ # to write to this cache
385
+ shared_file_lock (jobcachelock )
386
+ jobcachelock .seek (0 )
387
+ jobstatus = jobcachelock .read ()
388
+
389
+ if os .path .isdir (jobcache ) and jobstatus == "success" :
376
390
if docker_req and runtimeContext .use_container :
377
391
cachebuilder .outdir = runtimeContext .docker_outdir or random_outdir ()
378
392
else :
379
393
cachebuilder .outdir = jobcache
380
394
381
395
_logger .info ("[job %s] Using cached output in %s" , jobname , jobcache )
382
396
yield CallbackJob (self , output_callbacks , cachebuilder , jobcache )
397
+ # we're done with the cache so release lock
398
+ jobcachelock .close ()
383
399
return
384
400
else :
385
401
_logger .info ("[job %s] Output of job will be cached in %s" , jobname , jobcache )
402
+
403
+ # turn shared lock into an exclusive lock since we'll
404
+ # be writing the cache directory
405
+ upgrade_lock (jobcachelock )
406
+
386
407
shutil .rmtree (jobcache , True )
387
408
os .makedirs (jobcache )
388
409
runtimeContext = runtimeContext .copy ()
389
410
runtimeContext .outdir = jobcache
390
- open (jobcachepending , "w" ).close ()
391
411
392
- def rm_pending_output_callback (output_callbacks , jobcachepending ,
412
+ def update_status_output_callback (output_callbacks , jobcachelock ,
393
413
outputs , processStatus ):
394
- if processStatus == "success" :
395
- os .remove (jobcachepending )
414
+ # save status to the lockfile then release the lock
415
+ jobcachelock .seek (0 )
416
+ jobcachelock .truncate ()
417
+ jobcachelock .write (processStatus )
418
+ jobcachelock .close ()
396
419
output_callbacks (outputs , processStatus )
397
420
398
421
output_callbacks = partial (
399
- rm_pending_output_callback , output_callbacks , jobcachepending )
422
+ update_status_output_callback , output_callbacks , jobcachelock )
400
423
401
424
builder = self ._init_job (job_order , runtimeContext )
402
425
0 commit comments