{"id":20704328,"url":"https://github.com/osch/lua-mtstates","last_synced_at":"2026-03-17T13:40:03.787Z","repository":{"id":45424112,"uuid":"146506277","full_name":"osch/lua-mtstates","owner":"osch","description":"Multi-threading Lua states (see: https://github.com/osch/lua-mtstates#mtstates)","archived":false,"fork":false,"pushed_at":"2023-11-10T00:51:48.000Z","size":133,"stargazers_count":16,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-23T01:31:57.190Z","etag":null,"topics":["lua","lua-binding","lua-library","multithreading"],"latest_commit_sha":null,"homepage":"","language":"C","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/osch.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"zenodo":null}},"created_at":"2018-08-28T21:00:43.000Z","updated_at":"2024-11-04T23:35:56.000Z","dependencies_parsed_at":"2025-04-23T01:31:27.926Z","dependency_job_id":"f8657586-4627-4559-afb3-37e5d5fae844","html_url":"https://github.com/osch/lua-mtstates","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/osch/lua-mtstates","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osch%2Flua-mtstates","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osch%2Flua-mtstates/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osch%2Flua-mtstates/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osch%2Flua-mtstates/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osch","download_url":"https://codeload.github.com/osch/lua-mtstates/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osch%2Flua-mtstates/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273385033,"owners_count":25096020,"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","status":"online","status_checked_at":"2025-09-03T02:00:09.631Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["lua","lua-binding","lua-library","multithreading"],"created_at":"2024-11-17T01:11:57.453Z","updated_at":"2026-03-17T13:39:58.728Z","avatar_url":"https://github.com/osch.png","language":"C","readme":"# mtstates \n[![Licence](http://img.shields.io/badge/Licence-MIT-brightgreen.svg)](LICENSE)\n[![build status](https://github.com/osch/lua-mtstates/workflows/test/badge.svg)](https://github.com/osch/lua-mtstates/actions/workflows/test.yml)\n[![Build status](https://ci.appveyor.com/api/projects/status/v8t6rsf45dwt60pl/branch/master?svg=true)](https://ci.appveyor.com/project/osch/lua-mtstates/branch/master)\n[![Install](https://img.shields.io/badge/Install-LuaRocks-brightgreen.svg)](https://luarocks.org/modules/osch/mtstates)\n\n\u003c!-- ---------------------------------------------------------------------------------------- --\u003e\n\nInvoking interpreter states from multiple threads for the [Lua] scripting language.\n\nThis package provides a way to create new Lua states from within Lua for using\nthem in arbitrary threads. The implementation is independent from the\nunderlying threading library (e.g. [Lanes] or [lua-llthreads2]).\n\nThe general principle is to prepare a state by running a setup function within\nthis state that returns a callback function which afterwards can be called from\ndifferent threads. This can be useful in a thread-pool scenario when the number\nof states exceeds the number of available threads.\n\nThis package is also available via LuaRocks, see https://luarocks.org/modules/osch/mtstates.\n\n[Lua]:               https://www.lua.org\n[Lanes]:             https://luarocks.org/modules/benoitgermain/lanes\n[lua-llthreads2]:    https://luarocks.org/modules/moteus/lua-llthreads2\n[carray]:            https://github.com/osch/lua-carray\n\nSee below for full [reference documentation](#documentation).\n\n\u003c!-- ---------------------------------------------------------------------------------------- --\u003e\n\n#### Requirements\n\n   * Tested operating systems: Linux, Windows, MacOS\n   * Other Unix variants: could work, but untested, required are:\n      * gcc atomic builtins or C11 `stdatomic.h`\n      * `pthread.h` or C11 `threads.h`\n   * Tested Lua versions: 5.1, 5.2, 5.3, 5.4, luajit 2.0 \u0026 2.1\n\n\u003c!-- ---------------------------------------------------------------------------------------- --\u003e\n\n## Examples\n\nFor the examples [llthreads2](https://luarocks.org/modules/moteus/lua-llthreads2)\nis used as low level multi-threading implementation.\n\nFirst example: a state is constructed whose callback function is called from a\nparallel thread and from the main thread. The state is passed by integer id to the\ncreated thread. \n\n```lua\nlocal llthreads = require(\"llthreads2.ex\")\nlocal mtstates  = require(\"mtstates\")\n\nlocal state = mtstates.newstate(function(init)\n                                    local list = { init }\n                                    local cmds = {\n                                        add = function(arg) list[#list + 1] = arg end,\n                                        get = function() return table.concat(list, \" \") end\n                                    }\n                                    return function(cmd, ...)\n                                        return cmds[cmd](...)\n                                    end\n                                end,\n                                \"Hello\")\n\nlocal thread = llthreads.new(function(stateId)\n                                 local mtstates = require(\"mtstates\")\n                                 local state    = mtstates.state(stateId)\n                                 return state:call(\"add\", \"World\")\n                             end,\n                             state:id())\nthread:start()\nthread:join()\nassert(\"Hello World\" == state:call(\"get\"))\n```\n\nIt's not possible for concurrently runnings threads to call a state's callback function\nsimultaneously, therefore each call waits until the previous call is finished. It is \npossible to give a timeout for calling a state callback (see [*state:tcall()*](#tcall)).\n\nThe following example uses  [mtmsg]( https://github.com/osch/lua-mtmsg#mtmsg) to\nsynchronize access to a state:\n\n```lua\nlocal llthreads = require(\"llthreads2.ex\")\nlocal mtmsg     = require(\"mtmsg\")\nlocal mtstates  = require(\"mtstates\")\n\nlocal state = mtstates.newstate(function()\n                                    local list = {}\n                                    local cmds = {\n                                        add = function(arg) list[#list + 1] = arg end,\n                                        get = function() return table.concat(list, \" \") end\n                                    }\n                                    return function(cmd, ...)\n                                        return cmds[cmd](...)\n                                    end\n                                end)\n\nlocal threadIn  = mtmsg.newbuffer()\nlocal threadOut = mtmsg.newbuffer()\n\nlocal thread = llthreads.new(function(threadInId, threadOutId)\n                                 local mtstates  = require(\"mtstates\")\n                                 local mtmsg     = require(\"mtmsg\")\n                                 local threadIn  = mtmsg.buffer(threadInId)\n                                 local threadOut = mtmsg.buffer(threadOutId)\n                                 while true do\n                                     local cmd, stateId, arg = threadIn:nextmsg()\n                                     if cmd == \"finish\" then\n                                         threadOut:addmsg(\"finished\")\n                                         break\n                                     else\n                                        local state = mtstates.state(stateId)\n                                        state:call(cmd, arg)\n                                        threadOut:addmsg(\"done\")\n                                    end\n                                 end\n                             end,\n                             threadIn:id(), threadOut:id())\nthread:start()\n\nthreadIn:addmsg(\"add\", state:id(), \"Hello\")\nthreadIn:addmsg(\"add\", state:id(), \"World\")\n\nassert(threadOut:nextmsg() == \"done\")\nassert(threadOut:nextmsg() == \"done\")\n\nassert(\"Hello World\" == state:call(\"get\"))\n\nthreadIn:addmsg(\"finish\")\nassert(threadOut:nextmsg() == \"finished\")\nassert(thread:join())\n```\n\n\n\n\u003c!-- ---------------------------------------------------------------------------------------- --\u003e\n\n## Documentation\n\n   * [Module Functions](#module-functions)\n       * mtstates.newstate()\n       * mtstates.state()\n       * mtstates.singleton()\n       * mtstates.id()\n       * mtstates.type()\n   * [State Methods](#state-methods)\n       * state:id()\n       * state:name()\n       * state:call()\n       * state:tcall()\n       * state:interrupt()\n       * state:isowner()\n       * state:close()\n   * [Errors](#errors)\n       * mtstates.error.ambiguous_name\n       * mtstates.error.concurrent_access\n       * mtstates.error.interrupted\n       * mtstates.error.invoking_state\n       * mtstates.error.object_closed\n       * mtstates.error.out_of_memory\n       * mtstates.error.state_result\n       * mtstates.error.unknown_object\n\n\u003c!-- ---------------------------------------------------------------------------------------- --\u003e\n\n### Module Functions\n\n* \u003cspan id=\"newstate\"\u003e**`mtstates.newstate([name,][libs,]setup[,...)`**\u003c/span\u003e\n\n  Creates a new state. The given setup function is executed in the new state. The\n  setup function must return a state callback function which can be called\n  using the method *state:call()*. Additional values that are returned from \n  the setup function are given back as additional results by *mtstates.newstate()*.\n  \n    * *name*  - optional string, the name of the new state, can be *nil* or \n                omitted to create a state without name.\n    * *libs*  - optional boolean, if *true* all standard libraries\n                are opened in the new state, if *false* only the basic lua functions \n                and the module \"package\" are loaded, other standard libraries \n                are preloaded and can be loaded by *require*, defaults to *true*,\n    * *setup* - state setup function, can be a function without upvalues or\n                a string containing lua code. The setup function must return\n                a function that is used as state callback function for the\n                new created state.\n    * *...*   - additional parameters, are transfered to the new state and\n                are given as arguments to the setup function. Arguments can be\n                simple data types (string, number, boolean, nil, light user data)\n                or [carray] objects.\n\n  This function returns a state referencing lua object with *state:isowner() == true*.\n  \n  State objects implement the [Notify C API], see [src/notify_capi.h](./src/notify_capi.h),\n  i.e. the state object has an an associated meta table entry *_capi_notify* delivered by\n  the C API function *notify_get_capi()* and the associated C API function *toNotifier()* returns\n  a valid pointer for a given state object. The notify will invoke the state's callback\n  function without arguments. See also [example06.lua](./examples/example06.lua).\n\n  State objects also implement the [Receiver C API], i.e. native code can pass \n  arguments to the state's callback function from any thread.\n\n  [Notify C API]:   https://github.com/lua-capis/lua-notify-capi\n  [Receiver C API]: https://github.com/lua-capis/lua-receiver-capi\n\n  Possible errors: *mtstates.error.invoking_state*,\n                   *mtstates.error.state_result*\n\n* **`mtstates.state(id|name)`**\n\n  Creates a lua object for referencing an existing state. The state must\n  be referenced by its *id* or *name*. Referencing the state by *id* is\n  much faster than referencing by *name* if the number of states \n  increases.\n\n    * *id* - integer, the unique state id that can be obtained by\n           *state:id()*.\n\n    * *name* - string, the optional name that was given when the\n               state was created with *mtstates.newstate()*. To\n               find a state by name the name must be unique for\n               the whole process.\n\n  This function returns a state referencing lua object with *state:isowner() == false*.\n\n  Possible errors: *mtstates.error.ambiguous_name*,\n                   *mtstates.error.unknown_object*\n\n\n* **`mtstates.singleton(name,[libs,]setup[,...)`**\n    \n  Creates a lua object for referencing an existing state by name. If the\n  state referenced by the name does not exist, a new state is created using\n  the supplied parameters. This invocation can be used to atomically construct \n  a globally named state securely from different threads.\n    \n  If *mtstates.singleton()* is called with the same *name* parameter from\n  concurrently running threads the second invocation waits until the first\n  invocation is finished or fails.\n  \n  \n  * *name* - mandatory string, name for finding an existing state. If the state is\n             not found the following parameters are used to create a new state\n             with the given name.\n  \n  * *libs*, *setup*, *...*  - same parameters as in [*mtstates.newstate()*](#newstate).\n  \n  This function returns a state referencing lua object with *state:isowner() == true*.\n\n  This function should only by used for very special use cases: Because references \n  to the same *mtstates*'s state from different lua states are reference counted\n  special care has to be taken that no reference cycle is constructed using \n  *mtstates.singleton()*.\n\n  Possible errors: *mtstates.error.ambiguous_name*,\n                   *mtstates.error.invoking_state*,\n                   *mtstates.error.state_result*    \n  \n* **`mtstates.id()`**\n\n  Gives the state id of the currently running state invoking this function.\n  Returns `nil` if the current state was not constructed via \n  *mtstates.newstate()* or *mtstates.singleton()*.\n  \n  \n* **`mtstates.type(arg)`**\n\n  Returns the type of *arg* as string. Same as *type(arg)* for builtin types.\n  For *userdata* objects it tries to determine the type from the *__name* field in \n  the metatable and checks if the metatable can be found in the lua registry for this key\n  as created by [luaL_newmetatable](https://www.lua.org/manual/5.3/manual.html#luaL_newmetatable).\n\n  Returns *\"userdata\"* for userdata where the *__name* field in the metatable is missing\n  or does not have a corresponding entry in the lua registry.\n  \n  Returns *\"mtstates.state\"* if the arg is the state userdata type provided by this package.\n\n\n\u003c!-- ---------------------------------------------------------------------------------------- --\u003e\n\n### State Methods\n\n* **`state:id()`**\n  \n  Returns the state's id as integer. This id is unique among all states \n  for the whole process.\n\n* **`state:name()`**\n\n  Returns the state's name that was given to *mtstates.newstate()*.\n  \n\n* **`state:call(...)`**\n\n  Invokes the state callback function that was returned by the state setup function\n  when the state was created with *mtstates.newstate()*.\n  \n  * *...* - All argument parameters are transfered to the state and given to the state\n            callback function. Arguments can be simple data types (string, number,\n            boolean, nil, light user data) or [carray] objects.\n\n  If the state callback function is processed in a concurrently running thread the \n  *state:call()* method waits for the other call to complete before the state callback\n  function is invoked. See next method *state:tcall()* for calling a state with \n  timeout parameter.\n\n  Returns the results of the state callback function. Results can be simple data types \n  (string, number, boolean, nil, light user data) or [carray] objects.\n\n  Possible errors: *mtstates.error.interrupted*,\n                   *mtstates.error.invoking_state*,\n                   *mtstates.error.object_closed*,\n                   *mtstates.error.state_result*\n\n\n* \u003cspan id=\"tcall\"\u003e**`state:tcall(timeout, ...)`**\u003c/span\u003e\n\n  Invokes the state callback function that was returned by the state setup function\n  when the state was created with *mtstates.newstate()*.\n  \n  * *timeout* float, maximal time in seconds for waiting for a concurrently running\n              call of the state callback function to complete.\n              \n  * *...* - additional argument parameters are transfered to the state and given to the state\n            callback function. Arguments can be simple data types (string, number,\n            boolean, nil, light user data) or [carray] objects.\n\n  If the state could be accessed within the timeout *state:tcall()* returns the boolean\n  value *true* and all results from the state callback function. Results can be simple \n  data types (string, number, boolean, nil, light user data) or [carray] objects.\n  \n  Returns *false* if the state could not be accessed during the timeout.\n\n  Possible errors: *mtstates.error.interrupted*,\n                   *mtstates.error.invoking_state*,\n                   *mtstates.error.object_closed*,\n                   *mtstates.error.state_result*\n\n\n* **`state:interrupt([flag])`**\n\n  Interrupts the state by installing a debug hook that triggers an error\n  *mtstates.error.interrupted*. Therefore the interrupt only occurs when lua\n  code is invoked. If the state is within a c function the interrupt occurs\n  when the c function returns.\n\n  * *flag* - optional boolean, if not specified or *nil* the state\n             is only interrupted once, if *true* the state is interrupted\n             at every operation again, if *false* the state is no\n             longer interrupted.\n\n             \n* **`state:isowner()`**\n\n  Returns `true` if the state referencing lua object owns the referenced\n  state. If the last owning lua object is garbage collected the underlying \n  state is closed. \n  \n  State referencing objects constructed via *mtstates.newstate()* or\n  *mtstates.singleton()* are owning the state.\n  \n  State referencing objects constructed via *mtstates.state()* are \n  not owning the state.\n  \n\n* **`state:close()`**\n\n  Closes the underlying state and frees the memory. Every operation from any\n  referencing object raises a *mtstates.error.object_closed*. \n  \n  A closed state cannot be found via its name or id using the function\n  *mtstates.state()*.\n  \n  A closed state cannot be reactivated.\n\n  Possible errors: *mtstates.error.concurrent_access*\n\n\u003c!-- ---------------------------------------------------------------------------------------- --\u003e\n\n### Errors\n\n* All errors raised by this module are string values. Special error strings are\n  available in the table `mtstates.error`, example:\n\n  ```lua\n  local mtstates = require(\"mtstates\")\n  assert(mtstates.error.state_result == \"mtstates.error.state_result\")\n  ```\n  \n  These can be used for error evaluation purposes, example:\n  \n  ```lua\n  local mtstates = require(\"mtstates\")\n  local _, err = pcall(function() \n      mtstates.newstate(function() end) \n  end)\n  assert(err:match(mtstates.error.state_result))\n  ```\n\n* **`mtstates.error.ambiguous_name`**\n\n  More than one state was found for the given name to *mtstates.state()*.\n  To find a state by name, the state name must be unique among all states\n  in the whole process \n\n* **`mtstates.error.concurrent_access`**\n\n  Raised if *state:close()* is called while the state is processing a call \n  on a parallel running thread.\n\n* **`mtstates.error.interrupted`**\n\n  The state was interrupted by invoking the method *state:interrupt()*.\n\n* **`mtstates.error.invoking_state`**\n\n  An error ocurred when running code within another state, e.g. when the setup function\n  given to *mtstates.newstate()* is executed during state creation or when the\n  state callback function is called in *state:call()*.\n  \n  This error contains the traceback from within the state and the outer traceback from \n  context that called *mtstates.newstate()* or *state:call()*.\n  \n\n* **`mtstates.error.object_closed`**\n\n  An operation is performed on a closed state, i.e. the method\n  *state:close()* has been called.\n\n* **`mtstates.error.out_of_memory`**\n\n  State memory cannot be allocated.\n\n\n* **`mtstates.error.state_result`**\n\n  A result value from a function invoked in another state is invalid, e.g. the\n  setup function given to *mtstates.newstate()* returns no state callback or\n  a state callback or state setup function returns a value that cannot be \n  transferred back to the invoking state.\n\n* **`mtstates.error.unknown_object`**\n\n  A reference to an existing state (via *mtstates.state()*)\n  cannot be created because the object cannot be found by\n  the given id or name. \n  \n  All mtstates objects are subject to garbage collection and therefore a owning\n  reference to a created object is needed to keep it alive, example:\n\n  ```lua\n  local mtstates  = require(\"mtstates\")\n  local s1 = mtstates.newstate(\"return function() return 3 end\")\n  local id = s1:id()\n  local s2 = mtstates.state(id)\n  local s3 = mtstates.state(id)\n  assert(s1:isowner() == true)\n  assert(s2:isowner() == false)\n  assert(s3:isowner() == false)\n  s2 = nil\n  collectgarbage()\n  assert(s3:call() == 3)\n  assert(mtstates.state(id):id() == id)\n  s1 = nil\n  collectgarbage()\n  local _, err = pcall(function()\n      s3:call()\n  end)\n  assert(err:match(mtstates.error.object_closed))\n  local _, err = pcall(function()\n      mtstates.state(id)\n  end)\n  assert(err:match(mtstates.error.unknown_object))\n  ```\n  \n\nEnd of document.\n\n\u003c!-- ---------------------------------------------------------------------------------------- --\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosch%2Flua-mtstates","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosch%2Flua-mtstates","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosch%2Flua-mtstates/lists"}