@@ -4,28 +4,48 @@ import org.slf4j.Logger
44import org.veupathdb.lib.s3.s34k.errors.S34KError
55import org.veupathdb.lib.s3.s34k.objects.S3Object
66import kotlin.time.Duration.Companion.milliseconds
7+ import kotlin.time.Duration.Companion.seconds
78
89@Suppress(" NOTHING_TO_INLINE" )
910internal object MinIOHax {
10- var MaxDeleteAttempts = 5
11-
12- var SleepMillis = 1_000L
13-
11+ /* *
12+ * Maximum amount of time that the [delete] method should wait for an object's
13+ * deletion to be confirmed by MinIO.
14+ */
15+ var MaxDeleteAwaitTime = 10 .seconds
16+
17+ /* *
18+ * Amount of time to sleep between MinIO API call retries.
19+ */
20+ var RetrySleepTime = 1 .seconds
21+
22+ /* *
23+ * Max number of times an individual MinIO API call should be retried.
24+ */
1425 var RetryCount = 3
1526
16- fun sleepCountDuration (sleepCount : Int ) = (sleepCount * SleepMillis ).milliseconds
17-
27+ /* *
28+ * Executes the given [action], retrying up to [RetryCount] times if the
29+ * call fails due to an error from MinIO.
30+ *
31+ * @param actionMsg Provider for a message describing the action being
32+ * performed. This will be called if necessary to write log lines.
33+ *
34+ * @param action MinIO interaction function to try to execute.
35+ *
36+ * @return The value returned by `action` on successful execution.
37+ *
38+ * @throws S34KError If the given [action] fails [RetryCount] times due to
39+ * MinIO errors. This exception will contain all MinIO exceptions thrown
40+ * during executions of the `action` function.
41+ */
1842 context(log: Logger )
19- fun <T > withRetries (actionMsg : () -> String , fn : () -> T ) =
20- withRetries(RetryCount , actionMsg, fn)
21-
22- context(log: Logger )
23- fun <T > withRetries (retries : Int , actionMsg : () -> String , fn : () -> T ): T {
43+ fun <T > withRetries (actionMsg : () -> String , action : () -> T ): T {
2444 var lastError: S34KError ? = null
2545
26- for (i in 1 .. retries ) {
46+ for (i in 1 .. RetryCount ) {
2747 try {
28- return fn ()
48+ return action ()
2949 } catch (e: S34KError ) {
3050 log.warn(" failed {} time(s) to {}" , i, actionMsg())
3151
@@ -42,33 +62,67 @@ internal object MinIOHax {
4262 throw lastError!!
4363 }
4464
65+ /* *
66+ * Sleeps for the duration configured by [RetrySleepTime].
67+ */
4568 context(log: Logger )
4669 fun sleep () {
47- log.debug(" sleeping for {}" , { SleepMillis .milliseconds })
48- Thread .sleep(SleepMillis )
70+ log.debug(" sleeping for {}" , { RetrySleepTime })
71+ Thread .sleep(RetrySleepTime .inWholeMilliseconds )
4972 }
5073
74+ /* *
75+ * Convenience method for calling [delete] on an S3Object.
76+ *
77+ * @param obj Object to delete from MinIO.
78+ */
5179 context(log: Logger )
5280 fun delete (obj : S3Object ) = delete(obj.path, obj::delete, obj::exists)
5381
82+ /* *
83+ * Tells MinIO to delete an object, then waits for MinIO to confirm the
84+ * object's deletion, sleeping for [RetrySleepTime] milliseconds between each
85+ * test for the target object's existence.
86+ *
87+ * This method wraps both the given [statFn] and [deleteFn] functions using
88+ * [withRetries] to retry all interactions with MinIO up to [RetryCount]
89+ * times.
90+ *
91+ * @param path Path of the object to be deleted. Used for logging.
92+ *
93+ * @param deleteFn Function to be called to delete the object from MinIO.
94+ *
95+ * @param statFn Function to be called to test if the object still exists in
96+ * MinIO.
97+ *
98+ * @throws S34KError If either of the given functions fail [RetryCount] times
99+ * due to MinIO errors. This exception will contain all MinIO exceptions
100+ * thrown during executions of the failing function.
101+ */
54102 context(log: Logger )
55103 fun delete (path : String , deleteFn : () -> Unit , statFn : () -> Boolean ) {
104+ val startTime = currentSystemMillis()
105+ var delta = 0 .milliseconds
106+
56107 // Tell minio to delete the object
57- deleteFn( )
108+ withRetries({ " delete object $path " }, deleteFn )
58109
59- // Sleep for a bit to let minio ponder the delete request
60- sleep()
110+ // Sleep to let minio ponder the delete request
111+ while (delta < MaxDeleteAwaitTime ) {
112+ sleep()
113+ delta = currentSystemMillis() - startTime
61114
62- // If minio is being particularly dim, give it some more time to work it out
63- for (i in 1 .. MaxDeleteAttempts ) {
64- if (! statFn())
115+ if (! withRetries({ " stat object $path " }, statFn))
65116 return
66117
67- log.debug(" waiting for object {} deletion: {}" , path, sleepCountDuration(i))
68- sleep()
118+ log.debug(" waiting for object {} deletion: {}" , path, delta)
69119 }
70120
71- log.error(" waited {} for object {} deletion, but MinIO reports that it still exists" , sleepCountDuration(MaxDeleteAttempts ), path)
121+
122+ log.error(" waited {} for object {} deletion, but MinIO reports that it still exists" , delta, path)
123+
72124 throw RuntimeException (" object deletion timeout for MinIO object $path " )
73125 }
126+
127+ private fun currentSystemMillis () = System .currentTimeMillis().milliseconds
74128}
0 commit comments