Package com.suparnatural.core.concurrency

Types

AtomicReference
Link copied to clipboard

An AtomicReference uses CAS instruction to get and update the wrapped volatile variable to ensure lock free thread safety.

Examples

val person = AtomicReference(toImmutable(Person()))
val newPerson = toImmutable(Person())
val value = person.value
person.compareAndSet(value, newPerson)

common
class AtomicReference<T>(value: T)
ios
typealias AtomicReference = <ERROR CLASS><T>
BackgroundWorker
Link copied to clipboard
ios
class BackgroundWorker(worker: Worker) : Worker
DeferredFuture
Link copied to clipboard
ios

A future whose value of type T can be set later. Internally, it uses a mutex lock which is locked as soon as the instance is created. Once the value is set, the lock is released. Therefore, any callers of await are blocked until value is set.

class DeferredFuture<T> : Future<T>
Future
Link copied to clipboard
common

A Future defers the resolution of a value to the future. The value can be consumed by calling Future.await method which is expected to block the current thread. Therefore, never call Future.await on the same thread as it will result in a dead lock.

interface Future<T>
Immutability
Link copied to clipboard
common

A property delegate to create thread safe immutable frozen properties backed by AtomicReference. The property can be declared with by [Immutability] (initialValue) keyword. All get and set operations are then performed atomically. Note that the values are immutable once set. So any updates to the internal properties of the value will cause a failure.

This is really useful if you want an immutable object so that it can be shared among different threads with some dynamic behavior (like lazy initialization)

Examples

// Since top level objects are always immutable, they can be accessed from any thread.
object SharedObject{

val person: Person? by Immutability<Person?>(initialValue = null)

fun initialize(p: Person) { // any thread can now atomically update the person property.
person = p // will succeed
person.name = "" // will cause error
}
}

class MyClass {
val value: Int by Immutability<Int>(initialValue = 0)
}

// can be called from any thread as long as instance is thread shareable.
instance.value = 3

class Immutability<T>(initialValue: T)
Job
Link copied to clipboard
common

A data structure to pass a payload and a callback job safely between threads by freezing its contents. The payload of type Input can be used by the consumer of this object for processing. The result of the processing of type Output can then be posted on a non capturing lambda job as argument.

Examples

val safeJob = ThreadTransferableJob.create("Hello") {it: Int ->
assertEquals("Hello".hashCode(), it)
}

// other thread which receives job as argument.

safeJob.job(safeJob.payload.hashCode())

class Job<Input, Output>
JobDispatcher
Link copied to clipboard
common

Dispatches a given job on a particular thread.

Examples

val future = JobDispatcher.dispatchOnMainThread("Hello") {it: String ->
assertEquals("Hello", it) // runs on main thread
}
future.await()

val future = JobDispatcher.dispatchOnBackgroundThread("Hello") {it: String ->
assertEquals("Hello", it) // runs on background thread
}
future.await()

object JobDispatcher
JobFuture
Link copied to clipboard
common

Wraps a job closure in a future. The job is not invoked until await is called. The jobInput is passed as the only argument to job.

class JobFuture<T, V>(job: (T) -> V, jobInput: T) : Future<V>
LooperWorker
Link copied to clipboard
class LooperWorker(looper: Looper)
MainWorker
Link copied to clipboard
ios
class MainWorker : Worker
MutexLock
Link copied to clipboard

A MutexLock is a locking mechanism which allows only one thread to gain access to a resource protected by an instance of MutexLock. If more than one thread tries to acquire lock, only the first thread is successful while the other threads either wait or return depending on whether lock or tryLock was invoked.

Examples

val mutex = MutexLock()
mutex.lock()
assertFalse(mutex.tryLock())
val future = WorkerFactory.newBackgroundWorker().execute(mutex) {
assertFalse(mutex.tryLock())
}
future.await()
mutex.unlock()
mutex.destroy()

common
class MutexLock
ios
class MutexLock
NativeFuture
Link copied to clipboard

A NativeFuture returns an instance of platform specific Future wrapped in a common API. Calling NativeFuture.await will wait until the result is available.

The platform specific implementations have their own constructors which wrap the native Future API.

common
class NativeFuture<T> : Future<T>
ios
class NativeFuture<T>(future: <ERROR CLASS><T>) : Future<T>
ReadWriteLock
Link copied to clipboard

A ReadWrite lock allows multiple readers to read a value and only one writer to update it. A read lock cannot be obtained until write lock is released. A write lock cannot be obtained until current readers don't release read lock.

Examples

val lock = ReadWriteLock()

// read from multiple threads simultaneously.
lock.acquireReadLock() // call from as many threads

// perform read ....

lock.releaseReadLock()

// to protect writes
lock.acquireWriteLock() // only one thread will get lock, others will be blocked.

// perform write ....

lock.releaseWriteLock() // next thread will now unblock.

common
class ReadWriteLock
ios
class ReadWriteLock
ResumableJob
Link copied to clipboard
common

A resumable job can be started in one worker and can be resumed in the original caller worker. For example, an API call can be started in a background worker but its results can be delivered on the main thread. The resumed job is a closure which takes the output of the original job as input. The caller does not need to wait for the background worker to complete just like co-routines as the resuming job is added to the event loop of the caller thread.

Note: This is an attempt to close the gap until multi-threaded coroutines become available on both platforms.

class ResumableJob<JobInput, ResultHandlerInput, ResultHandlerOutput>
ValueFuture
Link copied to clipboard
common

A naive future implementation to wrap a given result. It executes on the calling thread synchronously.

Examples

val future: Future<String> = ValueFuture("hello")
val result: String = future.await()

class ValueFuture<T>(result: T) : Future<T>
Worker
Link copied to clipboard
common

Worker presents a unified API across all platforms to interact with threads. A Worker can execute a job in its event loop. If needed, it can also resume the flow on a different worker instance. All threads including main are exposed via this API. For example, to get a Worker backed by main thread, use WorkerFactory.main.

A worker job must satisfy the following requirements:

  1. The job must be a non state capturing lambda which does not capture any outside state.

  2. Any input required by the job must be passed before hand in the execute or executeAndResume method jobInput parameters.

  3. The job input arguments must be treated as immutable to guarantee thread safety.

The basic idea behind worker is to bring the same level of abstraction to every platform as Native has because native concurrency is the most restrictive one.

On iOS, it uses the Kotlin/Native's Worker API. On Android, it uses Handler.

Examples

Run job on background Worker

val worker = WorkerFactory.newBackgroundThread()

// calling execute schedules a task on worker
val future = worker.execute("Hello") {it: String ->
assertEquals("Hello", it)
"World"
}
// wait for worker to complete, use await
val result: String = future.await()
assertEquals("World", result)

Resume job on a different Worker

val worker1 = WorkerFactory.newBackgroundWorker()
val worker2 = WorkerFactory.newBackgroundWorker()
val future = worker2.executeAndResume(INPUT, {
assertEquals(INPUT, it)
OUTPUT
}, worker1, true) {
assertEquals(OUTPUT, it)
it
}
assertEquals(OUTPUT, future.await())

Resume Job on main worker

val worker = WorkerFactory.newBackgroundWorker()
val future = worker.executeAndResume(INPUT, {
assertEquals(INPUT, it)
OUTPUT
}, awaitResumingJob = true) {
// called on main thread asynchronously
assertEquals(OUTPUT, it)
it
}

// do not call future.await because it will block main thread.

interface Worker
WorkerFactory
Link copied to clipboard

A factory which creates and returns Worker instances.

common
class WorkerFactory
ios
class WorkerFactory

Functions

isMainThread
Link copied to clipboard

Returns if the current thread is main thread.

Examples

JobDispatcher.dispatchOnMainThread(Unit) {
assertTrue(isMainThread())
}

JobDispatcher.dispatchOnBackgroundThread(Unit) {
assertFalse(isMainThread())
}

common
fun isMainThread(): Boolean
ios
fun isMainThread(): Boolean
toImmutable
Link copied to clipboard

Returns an immutable instance of an object.

On Android, it simply echoes the input back because threads can have shared state. On iOS, it calls .toImmutable method.

Examples

val person = Person(name = "Bob").toImmutable(

person.name = "Jerry" // error

common
fun <T> T.toImmutable(): T
ios
fun <T> T.toImmutable(): T