{"id":20036612,"url":"https://github.com/svermeulen/lusc","last_synced_at":"2025-05-05T05:31:54.421Z","repository":{"id":195922196,"uuid":"693672568","full_name":"svermeulen/lusc","owner":"svermeulen","description":"Structured Async/Concurrency for Lua","archived":false,"fork":false,"pushed_at":"2023-10-17T08:01:36.000Z","size":107,"stargazers_count":18,"open_issues_count":0,"forks_count":1,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-05-02T09:59:19.682Z","etag":null,"topics":["async","concurrency","coroutines","lua","structured-concurrency"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/svermeulen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-09-19T13:38:54.000Z","updated_at":"2025-02-04T01:09:35.000Z","dependencies_parsed_at":"2023-10-16T20:58:26.610Z","dependency_job_id":null,"html_url":"https://github.com/svermeulen/lusc","commit_stats":null,"previous_names":["svermeulen/lusc"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svermeulen%2Flusc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svermeulen%2Flusc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svermeulen%2Flusc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svermeulen%2Flusc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/svermeulen","download_url":"https://codeload.github.com/svermeulen/lusc/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252446496,"owners_count":21749246,"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","concurrency","coroutines","lua","structured-concurrency"],"created_at":"2024-11-13T10:14:30.214Z","updated_at":"2025-05-05T05:31:53.174Z","avatar_url":"https://github.com/svermeulen.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n# Lusc - Structured Async/Concurrency for Lua\n\nLusc brings the concepts of [Structured Concurrency](https://en.wikipedia.org/wiki/Structured_concurrency) to Lua.  The name is an abbrevriation of this (**LU**a **S**tructured **C**oncurrency).\n\nThis programming paradigm was first popularized by the python library [Trio](https://github.com/python-trio/trio) and Lusc basically mirrors the Trio API almost exactly.  So if you are already familiar with Trio then you should be able to immediately understand and use the Lusc API.\n\nIf you aren't familiar with Trio - then in short, Structured Concurrency makes asynchronous tasks an order of magnitude easier to manage.  It achieves this by making the structure of code match the hierarchical structure of the async operations, which results in many benefits.  For more details, you might check out the [trio docs](https://trio.readthedocs.io/en/stable/reference-core.html), or [these articles](https://gist.github.com/belm0/4c6d11f47ccd31a231cde04616d6bb22) (which this library was based on)\n\nInstallation\n---\n\n`luarocks install lusc`\n\nCompatibility\n---\n\nLusc is written in pure Lua, so should work on Linux/OSX/Windows, and does not use any Lua language features beyond Lua 5.1, so should be compatible with most Lua environments (including LuaJIT).\n\nSimple Example\n---\n\nRun multiple tasks in parallel:\n\n```lua\nlocal lusc = require(\"lusc\")\n\nlocal function main()\n   print(\"Waiting 1 second...\")\n   lusc.await_sleep(1)\n\n   print(\"Creating child tasks...\")\n\n   -- This will run both child tasks in parallel, so\n   -- the total time will be 1 second, not 2\n   lusc.open_nursery(function(nursery)\n      nursery:start_soon(function()\n         print(\"child 1 started.  Waiting 1 second...\")\n         lusc.await_sleep(1)\n         print(\"Completed child 1\")\n      end)\n\n      nursery:start_soon(function()\n         print(\"Child 2 started.  Waiting 1 second...\")\n         lusc.await_sleep(1)\n         print(\"Completed child 2\")\n      end)\n   end)\n   -- Note that the nursery will block here until all child tasks complete\n\n   print(\"Completed all child tasks\")\nend\n```\n\nFor more complex examples (eg. channels) please see the [lusc_luv tests here](https://github.com/svermeulen/lusc_luv/blob/main/gen/lusc_luv/tests/lusc_spec.lua).  If you're able to read through each of those tests, and understand what's going on, then you're in great shape to use Lusc in your own projects.\n\nRunning the examples\n---\n\nYou might notice that in the examples above, we are not calling the `main()` functions directly.  This is because, similar to trio, all `lusc.X` methods need to be executed underneath a `lusc.run` function.  However, unlike Trio, in order to call `lusc.run`, the user has to supply implementations for `sleep` and `get_time`. This is necessary since this functionality varies depending on the environment where you are running Lua.\n\n`lusc.run` takes a `time_provider` that should be a function that return the current time in seconds (which doesn't have to be the actual time, but just a time value that starts from some arbitrary point in the past) and also a `sleep_handler` function that performs a sleep with a given number of seconds.\n\nIf running in a Linux/OSX environment a simple way to achieve this would be the following (which you can execute for yourself by running the lua files in the `examples/` folder):\n\n```lua\n-- NOTE: Do not use this function in a real app\n\nlocal function run(entry_point)\n   lusc.run({\n      entry_point = entry_point,\n      time_provider = function()\n         return os.time()\n      end,\n      sleep_handler = function(seconds)\n         os.execute(\"sleep \" .. tostring(seconds))\n      end,\n   })\nend\n\nrun(main)\n```\n\nHowever - This approach has many limitations:\n* `os.time()` does not provide sub-second precision\n* `os.execute(\"sleep x\")` is not cross platform (windows would require a different command)\n* Using `os.execute` for sleep is a fairly heavy operation\n\nInstead, you should use lusc on top of something else that can provide better implementations for sleep() and get_time().\n\nFor example, if you are ok with adding a dependency to [Luv](https://github.com/luvit/luv) you can use [lusc_luv](https://github.com/svermeulen/lusc_luv) instead\n\nAPI Reference\n---\n\n```lua\n\n-- NOTE - The code here is not valid Lua code - it is Teal code, which gets\n-- compiled to Lua\n-- But can be used as reference for your lua code to understand the API and the methods/types\nlocal record lusc\n   record Channel\u003cT\u003e\n      --- Only needed when there is a buffer max size\n      -- @return true if the receiving side is closed, in which\n      -- case there is no need to send any more values\n      await_send:function(Channel\u003cT\u003e, value:T)\n\n      --- raises an error if the buffer is full\n      -- @return true if the receiving side is closed, in which\n      -- case there is no need to send any more values\n      send:function(Channel\u003cT\u003e, value:T)\n\n      --- @return true if both the sending side is closed and there are no more\n      -- @return received value\n      -- values to receive\n      await_receive_next:function(Channel\u003cT\u003e):T, boolean\n\n      --- Receives all values, until sender is closed\n      await_receive_all:function(Channel\u003cT\u003e):function():T\n\n      --- raises an error if nothing is there to receive\n      -- @return received value\n      -- @return true if both the sending side is closed and there are no more\n      -- values to receive\n      receive_next:function(Channel\u003cT\u003e):T, boolean\n\n      --- Indicates that the sender has completed and receiver can end\n      close:function(Channel\u003cT\u003e)\n\n      -- Just calls close() after the given function completes\n      close_after:function(Channel\u003cT\u003e, function())\n   end\n\n   record Opts\n      -- Default: false\n      generate_debug_names:boolean\n\n      -- err is nil when completed successfully\n      on_completed: function(err:ErrorGroup)\n\n      -- Optional - by default it uses luv timer\n      scheduler_factory: function():Scheduler\n   end\n\n   record ErrorGroup\n      errors:{any}\n      new:function({any}):ErrorGroup\n   end\n\n   record Task\n      record Opts\n         name:string\n      end\n\n      parent: Task\n   end\n\n   record Event\n      is_set:boolean\n\n      set:function(Event)\n      await:function(Event)\n   end\n\n   record CancelledError\n   end\n\n   record DeadlineOpts\n      -- note: can only set one of these\n      move_on_after:number\n      move_on_at:number\n      fail_after:number\n      fail_at:number\n   end\n\n   record CancelScope\n      record Opts\n         shielded: boolean\n         name:string\n\n         -- note: can only set one of these\n         move_on_after:number\n         move_on_at:number\n         fail_after:number\n         fail_at:number\n      end\n\n      record ShortcutOpts\n         shielded: boolean\n         name:string\n      end\n\n      record Result\n         was_cancelled: boolean\n         hit_deadline: boolean\n      end\n\n      cancel:function(CancelScope)\n   end\n\n   record Nursery\n      record Opts\n         name:string\n\n         shielded: boolean\n\n         -- note: can only set one of these\n         move_on_after:number\n         move_on_at:number\n         fail_after:number\n         fail_at:number\n      end\n\n      cancel_scope: CancelScope\n\n      -- TODO\n      -- start:function()\n\n      start_soon:function(self: Nursery, func:function(), Task.Opts)\n   end\n\n   open_nursery:function(handler:function(nursery:Nursery), opts:Nursery.Opts):CancelScope.Result\n   get_time:function():number\n   await_sleep:function(seconds:number)\n   await_until:function(until_time:number)\n   await_forever:function()\n   new_event:function():Event\n   run:function(opts:Opts)\n\n   -- If true, then the current code is being executed\n   -- under the lusc task loop and therefore lusc await\n   -- methods can be used\n   is_processing:function():boolean\n\n   move_on_after:function(delay_seconds:number, handler:function(scope:CancelScope), opts:CancelScope.ShortcutOpts):CancelScope.Result\n   move_on_at:function(delay_seconds:number, handler:function(scope:CancelScope), opts:CancelScope.ShortcutOpts):CancelScope.Result\n   fail_after:function(delay_seconds:number, handler:function(scope:CancelScope), opts:CancelScope.ShortcutOpts):CancelScope.Result\n   fail_at:function(delay_seconds:number, handler:function(scope:CancelScope), opts:CancelScope.ShortcutOpts):CancelScope.Result\n\n   cancel_scope:function(handler:function(scope:CancelScope), opts:CancelScope.Opts):CancelScope.Result\n\n   --- @return true if the given object is an instance of ErrorGroup\n   -- and also that it only consists of the cancelled error\n   is_cancelled_error:function(err:any):boolean\n\n   has_started:function():boolean\n\n   get_root_nursery:function():Nursery\n\n   cancel_all:function()\n   open_channel:function\u003cT\u003e(max_buffer_size:integer):Channel\u003cT\u003e\n\n   get_running_task:function():Task\n   try_get_running_task:function():Task\nend\n```\n\nMore Examples / Docs\n---\n\nFor further documentation/examples we recommend looking at \"lusc_spec.tl\" in this repo, which covers all the features of lusc.  You can run these tests using busted library by executing the script at `scripts/run_tests.sh`\n\nStrong Typing Support\n---\n\nNote that this library is implemented using [Teal](https://github.com/teal-language/tl) and that all the lua files here are generated.  If you are also using Teal, and want your calls to the lusc API strongly typed, you can copy and paste the teal type definition files from `/dist/lusc.d.tl` into your project (or just add a path directly to the source code here in your tlconfig.lua file)\n\nHistory / Credits\n---\n\nThis library is based on [this great series](https://gist.github.com/belm0/4c6d11f47ccd31a231cde04616d6bb22) of articles explaining how to add structured concurrency support to Lua by @belm0.  One difference with the implementation provided in the article is that this library is compatible with Lua 5.1 (since it does not use to-be-closed variables).  This was necessary to provide LuaJIT compatibility.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsvermeulen%2Flusc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsvermeulen%2Flusc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsvermeulen%2Flusc/lists"}