@@ -27,6 +27,8 @@ import java.nio.file.PathMatcher
27
27
import java.nio.file.WatchService
28
28
import java.nio.file.attribute.UserPrincipalLookupService
29
29
import java.time.Duration
30
+ import java.time.temporal.ChronoUnit
31
+ import java.util.function.Predicate
30
32
31
33
import com.azure.core.util.polling.SyncPoller
32
34
import com.azure.storage.blob.BlobServiceClient
@@ -35,9 +37,16 @@ import com.azure.storage.blob.models.BlobCopyInfo
35
37
import com.azure.storage.blob.models.BlobItem
36
38
import com.azure.storage.blob.models.BlobStorageException
37
39
import com.azure.storage.blob.models.ListBlobsOptions
40
+ import dev.failsafe.Failsafe
41
+ import dev.failsafe.RetryPolicy
42
+ import dev.failsafe.event.EventListener
43
+ import dev.failsafe.event.ExecutionAttemptedEvent
44
+ import dev.failsafe.function.CheckedSupplier
38
45
import groovy.transform.CompileStatic
46
+ import groovy.transform.Memoized
39
47
import groovy.transform.PackageScope
40
48
import groovy.util.logging.Slf4j
49
+ import nextflow.cloud.azure.config.AzConfig
41
50
/**
42
51
* Implements a file system for Azure Blob Storage service
43
52
*
@@ -69,7 +78,7 @@ class AzFileSystem extends FileSystem {
69
78
@PackageScope AzFileSystem () {}
70
79
71
80
@PackageScope
72
- AzFileSystem (AzFileSystemProvider provider , BlobServiceClient storageClient , String bucket ) {
81
+ AzFileSystem (AzFileSystemProvider provider , BlobServiceClient storageClient , String bucket ) {
73
82
this . provider = provider
74
83
this . containerName = bucket
75
84
this . storageClient = storageClient
@@ -109,6 +118,10 @@ class AzFileSystem extends FileSystem {
109
118
}
110
119
111
120
private Iterable<? extends Path > listContainers () {
121
+ return apply(()-> listContainers0())
122
+ }
123
+
124
+ private Iterable<? extends Path > listContainers0 () {
112
125
final containers = new ArrayList ()
113
126
storageClient
114
127
.listBlobContainers()
@@ -359,18 +372,18 @@ class AzFileSystem extends FileSystem {
359
372
boolean exists = false
360
373
boolean isDirectory = false
361
374
362
- def opts = new ListBlobsOptions ()
375
+ final opts = new ListBlobsOptions ()
363
376
.setPrefix(path. blobName())
364
377
.setMaxResultsPerPage(10 )
365
378
try {
366
- def values = path. containerClient(). listBlobs(opts,null ). iterator()
379
+ final values = apply(() -> path. containerClient(). listBlobs(opts,null ). iterator() )
367
380
368
381
final char SLASH = ' /'
369
382
final String name = path. blobName()
370
383
371
384
int count= 0
372
- while ( values. hasNext() ) {
373
- BlobItem blob = values. next()
385
+ while ( apply(() -> values. hasNext() ) ) {
386
+ BlobItem blob = apply(() -> values. next() )
374
387
if ( blob. name == name )
375
388
exists = true
376
389
else if ( blob. name. startsWith(name) && blob. name. charAt(name. length())== SLASH ) {
@@ -425,7 +438,7 @@ class AzFileSystem extends FileSystem {
425
438
}
426
439
427
440
@PackageScope
428
- AzFileAttributes readAttributes (AzPath path ) {
441
+ AzFileAttributes readAttributes (AzPath path ) {
429
442
final cache = path. attributesCache()
430
443
if ( cache )
431
444
return cache
@@ -496,5 +509,42 @@ class AzFileSystem extends FileSystem {
496
509
return false
497
510
}
498
511
}
499
-
512
+
513
+
514
+ /**
515
+ * Creates a retry policy using the configuration specified by {@link nextflow.cloud.azure.config.AzRetryConfig}
516
+ *
517
+ * @param cond A predicate that determines when a retry should be triggered
518
+ * @return The {@link dev.failsafe.RetryPolicy} instance
519
+ */
520
+ @Memoized
521
+ protected <T> RetryPolicy<T> retryPolicy (Predicate<? extends Throwable > cond ) {
522
+ final cfg = AzConfig . getConfig(). retryConfig()
523
+ final listener = new EventListener<ExecutionAttemptedEvent<T> > () {
524
+ @Override
525
+ void accept (ExecutionAttemptedEvent<T> event ) throws Throwable {
526
+ log. debug(" Azure I/O exception - attempt: ${ event.attemptCount} ; cause: ${ event.lastFailure?.message} " )
527
+ }
528
+ }
529
+ return RetryPolicy . < T> builder()
530
+ .handleIf(cond)
531
+ .withBackoff(cfg. delay. toMillis(), cfg. maxDelay. toMillis(), ChronoUnit . MILLIS )
532
+ .withMaxAttempts(cfg. maxAttempts)
533
+ .withJitter(cfg. jitter)
534
+ .onRetry(listener)
535
+ .build()
536
+ }
537
+
538
+ /**
539
+ * Carry out the invocation of the specified action using a retry policy
540
+ * when {@code TooManyRequests } Azure Batch error is returned
541
+ *
542
+ * @param action A {@link dev.failsafe.function.CheckedSupplier} instance modeling the action to be performed in a safe manner
543
+ * @return The result of the supplied action
544
+ */
545
+ protected <T> T apply (CheckedSupplier<T> action ) {
546
+ final cond = (e -> e instanceof IOException ) as Predicate<? extends Throwable >
547
+ final policy = retryPolicy(cond)
548
+ return Failsafe . with(policy). get(action)
549
+ }
500
550
}
0 commit comments