Blocking Operations Within Script

To avoid concurrency issues with mutable state, the scripting engine is designed as an Active Object with operations performing on a single thread. Therefore special care needs to be taken while performing blocking operations within the script, since blocking the executing thread will also prevent other tasks, such as event handlers, from being executed and could lead to unexpected deadlocks.

See the Sequencer Technical Documentation for more information on the Scripting Engine design and Active Objects.

Note

All the CSW related DSL’s provided in the scripting environment do not block main script thread. Hence, they can be used directly without worrying about stalling the script.

There is one exception to this: callback based API’s, such as an onEvent handler. Here, you would not want to perform CPU/IO intensive tasks without following one of the patterns mentioned below.

There are three types of blocking operations identified here, each with its own patterns and recommendations to be followed while performing it.

  1. CPU Bound
  2. IO Bound
  3. Sleep

To handle the first two types of blocking operations, the DSL provide the blockingCpu and blockingIo constructs. These constructs cause the script execution engine to use different thread pools instead of the main script thread to execute the blocking code.

You can read more about these patterns of blocking here

DO NOT ACCESS/UPDATE MUTABLE STATE

The techniques mentioned here for performing blocking tasks causes them to be executed on a different thread than the main sequencer script. Therefore, accessing/updating mutable state defined in the main script from these blocking functions is not thread safe.

We recommend creating separate kotlin (.kt) file/files and maintaining all the CPU/IO bound blocking tasks there, to reduce the risk of improperly accessing mutable state.

CPU Bound

For any CPU bound operations follow these steps:

  1. Create a new function and mark it with the suspend keyword.
  2. Wrap the function body inside a blockingCpu utility function.
  3. Call the new function. Wrap it in an async block to run it in the background.

The following example demonstrates writing a CPU bound operation. In this example, the method BigInteger.probablePrime(4096, Random()) is CPU bound and takes more than few seconds to finish. We wrap it in another method, findBigPrime and mark it with the suspend keyword.

Kotlin
source// Calculating probablePrime is cpu bound operation and should be wrapped inside blockingCpu utility function
// Following function takes around 10 seconds to find a 4096 bit length prime number
suspend fun findBigPrime(): BigInteger =
        blockingCpu {
            BigInteger.probablePrime(4096, Random())
        }

The following shows the usage of the above compute heavy function in main sequencer script in two ways. First it is called in the main script flow causing the main script to pause until the method completes with a value. Note that background tasks continue to execute while the method is being executed. In the second way, an async block is used, which causes the method to be executed in the background. The async block returns a Deferred type, and the Deferred.await() method can be used to pause script execution until the async block completes without blocking the other background processes.

Kotlin
sourcescript {

    loopAsync(100.milliseconds) {
        // loop represents the computation running on the main script thread.
    }

    onSetup("prime number") {
        // by default calling findBigPrime cpu intensive task suspends and waits for result
        // but this runs on different thread than the main script thread
        // which allows other background tasks started previously to run concurrently
        val bigPrime1: BigInteger = findBigPrime()

        // if you want to run findBigPrime in the background, then wrap it within async
        val bigPrimeDeferred: Deferred<BigInteger> = async { findBigPrime() }
        // ...
        // wait for compute intensive operation to finish which was previously started
        val bigPrime2: BigInteger = bigPrimeDeferred.await()

        // script continues...
    }
}

IO Bound

For any IO bound operations follow these steps:

  1. Create a new function and mark that with suspend keyword.
  2. Wrap function body inside blockingIo utility function.
  3. Call the new function. Wrap it in an async block to run it in the background.

The following example demonstrates writing IO bound operation. In this example the method BufferredReader.readLine() is IO bound and takes more than few seconds to finish. We wrap it in another method, readMessage and mark it with the suspend keyword.

Kotlin
source// Reading a line from a file is blocking IO operation and should be wrapped inside blockingIo utility function
suspend fun readMessage(bufferedReader: BufferedReader): CharSequence? =
        blockingIo {
            bufferedReader.readLine()
        }

The following shows the usage of the above IO heavy function in main sequencer script, again in two ways: in main script flow and also using an async block to execute in the background.

Kotlin
sourcescript {
    loopAsync(100.milliseconds) {
        // loop represents the computation running on the main script thread.
    }

    onSetup("read file") {
        val reader = File("someFile.txt").bufferedReader()

        // by default calling readMessage (blocking io) task suspends and waits for result
        // but this runs on different thread than the main script thread
        // which allows other background tasks started previously to run concurrently
        val message1 = readMessage(reader)

        // if you want to run readMessage in the background, then wrap it within async
        val message2Deferred = async { readMessage(reader) }
        // ...
        // wait for blocking operation to finish which was previously started
        val message2: CharSequence? = message2Deferred.await()

        // script continues...
    }
}

Sleep

There may be cases where you simply need to pause execution of your script for a specific amount of time. Using a traditional sleep or wait command performs as expects, pausing the executing thread, but due to the nature of the Active Object, these block background handlers as well. Therefore, the Kotlin Coroutine package provides a method, delay, that should be used to prevent this blocking. Like sleep, delay takes the number of milliseconds to pause as an argument.