Search this site
Embedded Files
io.turbodsl
  • Home
    • Fundamentals
    • Default Mechanisms
    • Sync vs. Async Scopes
    • Asynchronous Results
    • Asynchronous Modes
    • Retry Pattern
    • Code Reusability
    • Collections
    • Performance Efficiency
io.turbodsl

Previous: Asynchronous Modes

Next: Code Reusability

Retry Patterns

Retryable Operations
Retrying Asynchronous Code

Retryable Operations 

A unit-of-work could potentially be retried if the expected operation may experience transient failures:

  • Unhandled errors / exceptions

  • Time-based conditions

  • IO operations (network, file system)

io.turbodsl provides retry mechanisms through a RetryScope:

  • retry {...} → retry any arbitrary block of code.

  • This expressions accept the following attributes:

    • retries → Number of additional executions - default: 1

      • Negative values will throw a ScopeImplementationException.

    • retryDelay → A delay per retry - default: 0 milliseconds

    • retryDelayFactor → A factor to apply on retryDelay on each retry attempt:

      • x=1.0 → retryDelay is the same for each retry - this is the default.

      • x>1.0 → retryDelay is increased for each retry.

      • 0<x<1.0 → retryDelay is decreased for each retry.

      • x=0 → retryDelay is applied only on first retry.

      • -1.0<x<0 → retryDelay is applied for odd-retries only, decreasing for each retry.

      • x=-1.0 → retryDelay is the same and applied for odd-retries only.

      • x<-1.0 → retryDelay is applied for odd-retries only, increasing for each retry.

      • Formula: retryDelay * retryDelayFactor.pow(retryCount - 1)

    • retryMode → how retries are managed:

      • OnTimeoutOnly → only retry if a timeout is detected.

      • OnErrorOnly → only retry if an error other than a timeout is detected.

      • Always → always retry on any error, even on timeout - this is the default.

      • Never → never retry - just to simplify enabling/disabling retry mechanisms during development / debug activities.

    • If last retry fails, a ScopeRetryException is thrown.


// Returns a String

TurboScope.execute {

   // Retry 5 times whenever AsyncJobs take more than 2 seconds.

   // Any other error will be thrown.

   // Delay each retry by 1, 2, 4, 8, 16 seconds respectively.

   // retry#1: 1_000L * 2_000L.pow(1 - 1) ->  1_000L

   // retry#2: 1_000L * 2_000L.pow(2 - 1) ->  2_000L

   // retry#3: 1_000L * 2_000L.pow(3 - 1) ->  4_000L

   // retry#4: 1_000L * 2_000L.pow(4 - 1) ->  8_000L

   // retry#5: 1_000L * 2_000L.pow(5 - 1) -> 16_000L

   // If last retry fails on timeout, return "INVALID"

   retry(

       retryMode = RetryScope.RetryMode.OnTimeoutOnly,

       retries = 5,

       timeout = 2_000L,

       retryDelay = 1_000,

       retryDelayFactor = 2.0,

       default = "INVALID".toDefault()

   ) {

       // Any Kotlin expressions / statements

       // Or even additional io.turbodsl expressions

       job { ... }

       job { ... }

       :

   }

}

Retrying Asynchronous Code

AsyncScopes can be retried without problems, retrying the all registered AsyncJobs. Note that using retry {...}  within the build phase is not allowed.
Therefore, retry expression has been marked as deprecated and will throw a ScopeImplementationException:

TurboScope.execute {

   async {

       retry { // deprecated / not-allowed

           asyncJob { ... }

           asyncJob { ... }

           :

       }

   }

   async(

       build = {

           retry { // deprecated / not-allowed

               asyncJob { ... }

               asyncJob { ... }

               :

           }

       }

   ) { ok, r ->

      :

   }

}

If you need to retry specific a specific AsyncJob, then you must include retry {...}  within the AsyncJob implementation:

TurboScope.execute {

   async {

       // Each asyncJob has different requirements for retry.

       asyncJob<SomeType1> {

           retry(...) { ... }

       }

       asyncJob { ... }

       asyncJob<SomeType2> {

           retry(...) { ... }

       }

       :

   }

   async(

       build = {

           // Each asyncJob has different requirements for retry.

           asyncJob<SomeType3> {

               retry(...) { ... }

           }

           asyncJob { ... }

           asyncJob<SomeType4> {

               retry(...) { ... }

           }

           :

       }

   ) { ok, r ->

       :

   }

}

The main reasons for these constraints are related to how AsyncScopes register, schedule, and execute AsyncJobs:

  1. The lambas specified for build should not be retried, otherwise it could potentially affect the overall results.

  2. Attempting to keep track of which AsyncJobs to re-execute adds unnecessary complexity.

Notice that expressions async(job1=..., job2=..., ...) do not expose the "build" phase making them easier to use:

TurboScope.execute<Unit> {

   retry(...) {

       async(

           // Each asyncJob has different requirements for retry.

           job1 = asyncJob<SomeType1> { ... },

           job2 = asyncJob<SomeType2> { ... },

           job3 = asyncJob<SomeType3> {

               retry(...) { ... }

           },

           job4 = asyncJob<String> {

               retry(...) { ... }

           },

       ) { ok, r1, r2, r3, r4 ->

           :

       }

   }

}


Previous: Asynchronous Modes

Next: Code Reusability

Copyright 2024 © migueltt
Google Sites
Report abuse
Google Sites
Report abuse