{"id":17862730,"url":"https://github.com/stevana/coroutine-state-machines","last_synced_at":"2025-08-20T21:23:35.913Z","repository":{"id":79986683,"uuid":"583571137","full_name":"stevana/coroutine-state-machines","owner":"stevana","description":"State machines with async I/O capabilities","archived":false,"fork":false,"pushed_at":"2023-04-13T08:33:57.000Z","size":27,"stargazers_count":11,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-17T19:11:22.459Z","etag":null,"topics":["async-io","event-loop","state-machine"],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stevana.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-30T07:19:16.000Z","updated_at":"2024-08-31T04:52:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"f40659c8-1e02-47c8-8d2a-03989339716f","html_url":"https://github.com/stevana/coroutine-state-machines","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevana%2Fcoroutine-state-machines","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevana%2Fcoroutine-state-machines/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevana%2Fcoroutine-state-machines/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stevana%2Fcoroutine-state-machines/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stevana","download_url":"https://codeload.github.com/stevana/coroutine-state-machines/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244717197,"owners_count":20498281,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async-io","event-loop","state-machine"],"created_at":"2024-10-28T08:54:49.941Z","updated_at":"2025-03-21T00:30:41.471Z","avatar_url":"https://github.com/stevana.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# coroutine-state-machines\n\nState machines of the type `Input -\u003e State -\u003e (Output, State)` are great. They\nare easy to reason about, and if run on a separate thread with access to a queue\nof `Input`s they perform well too.\n\nSometimes the state machine might need to do some blocking I/O before producing\nthe output though, this slows down the processing of inputs.\n\nThis repo is an experiment in how we can write the state machine as if the I/O\nis blocking, but actually it's non-blocking and inputs can continue to be\nprocesses while we wait for the I/O action to complete.\n\n## Usage\n\nTo make things more concrete we will be implementing a key-value store as a\nstate machine.\n\nTo start the key-value store in a terminal issue:\n\n```bash\ncabal run app\n```\n\nThen interact with the key-value store from another terminal using `Write` and\n`Read` commands as follows:\n\n```bash\n$ http POST :8080 --raw 'Write \"x\" 1'\nHTTP/1.1 200 OK\nDate: Thu, 05 Jan 2023 08:47:03 GMT\nServer: Warp/3.3.23\nTransfer-Encoding: chunked\n\nOk\n\n$ http POST :8080 --raw 'Read \"x\"'\nHTTP/1.1 200 OK\nDate: Thu, 05 Jan 2023 08:47:04 GMT\nServer: Warp/3.3.23\nTransfer-Encoding: chunked\n\nResult 1\n```\n\n## How it works\n\nThe state machine for the key-value store example looks like this:\n\n```haskell\ndata Input = Write String Int | Read String\n  deriving stock (Show, Read)\n\ndata Output = Ok | Result (Maybe Int)\n  deriving stock Show\n\nsm :: SM (Map String Int) Input Output\nsm = do\n  i \u003c- ask\n  case i of\n    Write k v -\u003e do\n      fsAppend k v\n      modify (Map.insert k v)\n      return Ok\n    Read k -\u003e do\n      m \u003c- get\n      return (Result (m Map.!? k))\n```\n\nWhere `fsAppend` appends the key-value pair to a file, so that we can recover in\nin-memory state in case of a crash.\n\nThe program looks sequential, but once the state machine hits the `fsAppend` it\nwill suspend using a coroutine monad, yielding control back to the event loop\nwhich feeds it inputs, the event loop will enqueue the I/O action to a separate\nthread that deals with I/O and continue feeding the state machine new inputs,\nuntil the I/O thread completes the write to disk, at which point the state\nmachine will be resumed with the latest state.\n\n## Contributing\n\nAny feedback, comments or suggestions are most welcome!\n\nIn particular if you know how to solve this problem in a different or better\nway.\n\nA potential source of confusion and bugs might be the fact that once we resume\nthe state might not be the same as it was before we suspended. It's not clear to\nme how big of a problem this is in practice, or if anything can be done about it\nwithout sacrificing either the \"sequential feel\" or the parallelism?\n\nOne possible generalisation that seems feasible is to not suspend immediately\nupon the I/O action, but rather merely return a \"future\" which we later can\n`await` for. This would allow us to do suspend and do multiple I/O actions\nbefore resuming, something like:\n\n```haskell\n  a1 \u003c- fsAppend k v\n  a2 \u003c- someOtherIOAction\n  awaitBoth a1 a2 -- or awaitEither a1 a2\n```\n\nArguably the await makes it more clear where the suspension and resumption\nhappen, which could help against the confusion regarding that the state might\nchange.\n\n## See also\n\n* *Development and Deployment of Multiplayer Online Games, Vol. II* by Sergey\n  Ignatchenko (2020), especially chapter 5;\n* [*Implementing Co, a Small Language With Coroutines #3: Adding\n  Coroutines*](https://abhinavsarkar.net/posts/implementing-co-3/);\n* [*A Lambda Calculus With Coroutines and Heapless, Directly-Called\n  Closures*](https://ayazhafiz.com/articles/23/a-lambda-calculus-with-coroutines-and-heapless-closures);\n* [Small VMs \u0026 Coroutines](https://blog.dziban.net/coroutines/);\n* [Tina is a teeny tiny, header only, coroutine and job\n  library](https://github.com/slembcke/Tina);\n* [Protothreads](http://dunkels.com/adam/pt/);\n* [Proactor pattern](https://en.wikipedia.org/wiki/Proactor_pattern);\n* [WebAssembly\n  Reactors](https://github.com/bytecodealliance/wasmtime/blob/main/docs/WASI-rationale.md#why-not-async).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstevana%2Fcoroutine-state-machines","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstevana%2Fcoroutine-state-machines","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstevana%2Fcoroutine-state-machines/lists"}