Skip to content

Commit

Permalink
[docs] Add more info to Coroutines block
Browse files Browse the repository at this point in the history
  • Loading branch information
nsk90 committed Feb 26, 2024
1 parent e1d57bc commit c01d243
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 11 deletions.
1 change: 1 addition & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
ko_fi: nskfedotov
custom: https://yoomoney.ru/to/4100118569686448
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
[![Android Arsenal]( https://img.shields.io/badge/Android%20Arsenal-KStateMachine-green.svg?style=flat )]( https://android-arsenal.com/details/1/8276 )
[![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin)
![multiplatform support](https://img.shields.io/badge/multiplatform-jvm%20%7C%20android%20%7C%20ios-brightgreen)
[![Share on X](https://img.shields.io/badge/twitter-share-white?logo=x&style=flat)](https://twitter.com/intent/tweet?text=I%20like%20this%20library%20%0A%0Ahttps%3A%2F%2Fgithub.com%2Fnsk90%2Fkstatemachine%0A%0A%23kstatemachine)
[![Share on X](https://img.shields.io/badge/twitter-share-white?logo=x&style=flat)](https://twitter.com/intent/tweet?text=I%20like%20KStateMachine%20library%20%0A%0Ahttps%3A%2F%2Fgithub.com%2Fnsk90%2Fkstatemachine%0A%0A%23kstatemachine)
[![Share on Reddit](https://img.shields.io/badge/reddit-share-red?logo=reddit&style=flat)](https://www.reddit.com/submit?url=https%3A%2F%2Fgithub.com%2Fnsk90%2Fkstatemachine&title=I%20like%20KStateMachine%20library)


[Documentation](https://nsk90.github.io/kstatemachine) | [Quick start](#quick-start-sample) | [Samples](#samples) | [Install](#install) | [License](#license) | [Discussions](https://github.com/nsk90/kstatemachine/discussions)

Expand Down Expand Up @@ -62,7 +64,10 @@ State management features:
> [!NOTE]
> The library is in a development phase. You are welcome to propose useful features, or contribute to the project.
>
> Don't forget to push the ⭐ if you like this project.
>
> You can donate or become a sponsor to support the project, using ❤️ github-sponsors button.
## Quick start sample

Expand Down
37 changes: 30 additions & 7 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
* [Other exceptions](#other-exceptions)
* [Multithreading and concurrency](#multithreading-and-concurrency)
* [Kotlin Coroutines](#kotlin-coroutines)
* [Use single threaded CoroutineScope](#use-single-threaded-coroutinescope)
* [CoroutineContext preservation guarantee](#coroutinecontext-preservation-guarantee)
* [Additional kstatemachine-coroutines artifact](#additional-kstatemachine-coroutines-artifact)
* [Migration guide from versions older than v0.20.0](#migration-guide-from-versions-older-than-v0200)
* [Export](#export)
Expand Down Expand Up @@ -825,7 +827,7 @@ Calling `processEvent()` on destroyed machine will throw also.
KStateMachine is designed to work in single thread.
Concurrent modification of library classes will lead to race conditions.
See [kotlin coroutines](#kotlin-coroutines) section for more info regarding coroutines environment, and how
See [kotlin coroutines](#kotlin-coroutines) section for more info regarding coroutines environment, and how
the library helps you to support this requirement.
## Kotlin Coroutines
Expand All @@ -842,13 +844,32 @@ Note that `Blocking` versions internally use `kotlinx.coroutines.runBlocking` fu
may cause deadlocks if used not properly. That is why you should avoid using `Blocking` APIs from coroutines and
recursively (from library callbacks).
### Use single threaded `CoroutineScope`
When you create a state machine with `createStateMachine`/`createStateMachineBlocking` (with coroutines support)
functions you have to provide `CoroutineScope` on which machine will work,
functions you have to provide `CoroutineScope` on which machine will work,
this scope also contains `CoroutineContext` by coroutines design.
This is how you can control a thread where state machine works. The scope is considered to use single threaded
`CoroutineContext`.
Using multithreaded `CoroutineContext` like (`default` or `io`) will probably lead to race conditions,
this is not correct.
Single thread `CoroutineScope` samples:
```kotlin
CoroutineScope(newSingleThreadContext("single thread"))
CoroutineScope(Dispatchers.Main)
```
Using multithreaded `CoroutineContext` like `Dispatchers.Default` or `Dispatchers.IO` will lead to race
conditions, it is not correct.
Even `Dispatchers.Default.limitedParallelism(1)` that seems to be ok at glance,
does not provide guarantee that each coroutine will be executed on the same single thread, it only limits the amount of
used threads. So race condition still takes place, as nothing forces threads, running on different processor cores,
to update variable values in their processor core caches, so outdated values could be used from core cache. Other words,
one thread does not to know about variable changes made by other one. This known as __visibility guarantee__,
that `volatile` keyword provides on `jvm`.
### `CoroutineContext` preservation guarantee
Suspendable functions and their `Blocking` analogs internally switch current execution `СoroutineСontext`
(from which they are called) to state machines one, using `kotlinx.coroutines.withContext` or
Expand All @@ -857,14 +878,16 @@ This is `CoroutineContext` preservation guarantee that the library provides.
Note that if you created machine with the scope containing `kotlinx.coroutines.EmptyCoroutineContext` switching will not
be performed. So if the StateMachine is created with correct (meeting above conditions) scope it is safe to call
suspendable methods like `processEvent()` from any context/thread due to internal context preservation.
StateMachine that was created by `createStdLibStateMachine()` (without coroutines support) does not provide any context
switching and of course does NOT provide any `CotoutineContext` preservation guarantee.
StateMachine that was created by `createStdLibStateMachine()` (without coroutines support) does not perform any context
switching and of course does NOT provide any `CoroutineContext` preservation guarantee.
Multithreading is always complicated and hard to explain, so you can also check this sample
regarding working with state machine from coroutines running from multiple threads:
```kotlin
runBlocking { // defines non empty coroutine context for state machine
// runBlocking starts an infinite event loop on current running thread,
// so it produces correct single threaded CoroutineContext for a StateMachine.
runBlocking { // defines non-empty coroutine context for state machine
val machineThread = Thread.currentThread()
val machineScope = this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import kotlinx.coroutines.CoroutineScope
* @param scope be careful while working with threaded scopes as KStateMachine classes are not thread-safe.
* Usually you should use only single threaded scopes, for example:
*
* CoroutineScope(Dispatchers.Default.limitedParallelism(1))
* CoroutineScope(newSingleThreadContext("single threaded context"))
*
* Note that all calls to created machine instance should be done only from that thread.
*/
Expand Down
4 changes: 2 additions & 2 deletions tests/src/commonTest/kotlin/ru/nsk/kstatemachine/TestUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ fun createTestStateMachine(
init = init
)
CoroutineStarterType.COROUTINES_LIB_SINGLE_THREAD_DISPATCHER -> createStateMachineBlocking(
CoroutineScope(newSingleThreadContext("")),
CoroutineScope(newSingleThreadContext("test single thread context")),
name,
childMode,
start,
Expand All @@ -123,7 +123,7 @@ fun createTestStateMachine(
init = init
)
CoroutineStarterType.COROUTINES_LIB_DEFAULT_LIMITED_DISPATCHER -> createStateMachineBlocking(
CoroutineScope(Dispatchers.Default.limitedParallelism(1)),
CoroutineScope(Dispatchers.Default.limitedParallelism(1)), // does not guarantee same thread for each task
name,
childMode,
start,
Expand Down

0 comments on commit c01d243

Please sign in to comment.