Skip to content

Commit 21f45ff

Browse files
committed
Changes: 1) New function "mtstates.singleton" for constructing a named state atomically,
2) mtstates.state() returns a weak reference instead of owning references to underlying state object
1 parent c27a84e commit 21f45ff

File tree

9 files changed

+367
-221
lines changed

9 files changed

+367
-221
lines changed

README.md

Lines changed: 86 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ assert(thread:join())
141141
* [Module Functions](#module-functions)
142142
* mtstates.newstate()
143143
* mtstates.state()
144+
* mtstates.singleton()
144145
* mtstates.id()
145146
* mtstates.type()
146147
* [State Methods](#state-methods)
@@ -153,7 +154,6 @@ assert(thread:join())
153154
* [Errors](#errors)
154155
* mtstates.error.ambiguous_name
155156
* mtstates.error.concurrent_access
156-
* mtstates.error.cycle_detected
157157
* mtstates.error.interrupted
158158
* mtstates.error.invoking_state
159159
* mtstates.error.object_closed
@@ -186,53 +186,66 @@ assert(thread:join())
186186
are given as arguments to the setup function. Arguments can be
187187
simple data types (string, number, boolean, nil, light user data).
188188

189+
This function returns a state referencing lua object with *state:isowner() == true*.
190+
189191
Possible errors: *mtstates.error.invoking_state*,
190192
*mtstates.error.state_result*
191193

192194
* **`mtstates.state(id|name)`**
193-
* **`mtstates.state(name,[libs,]setup[,...)`**
194195

195-
* **First form:**
196-
197-
Creates a lua object for referencing an existing state. The state must
198-
be referenced by its *id* or *name*. Referencing the state by *id* is
199-
much faster than referencing by *name* if the number of states
200-
increases.
196+
Creates a lua object for referencing an existing state. The state must
197+
be referenced by its *id* or *name*. Referencing the state by *id* is
198+
much faster than referencing by *name* if the number of states
199+
increases.
200+
201+
* *id* - integer, the unique state id that can be obtained by
202+
*state:id()*.
203+
204+
* *name* - string, the optional name that was given when the
205+
state was created with *mtstates.newstate()*. To
206+
find a state by name the name must be unique for
207+
the whole process.
208+
209+
This function returns a state referencing lua object with *state:isowner() == false*.
210+
211+
Possible errors: *mtstates.error.ambiguous_name*,
212+
*mtstates.error.unknown_object*
213+
214+
215+
* **`mtstates.singleton(name,[libs,]setup[,...)`**
216+
217+
Creates a lua object for referencing an existing state by name. If the
218+
state referenced by the name does not exist, a new state is created using
219+
the supplied parameters. This invocation can be used to atomically construct
220+
a globally named state securely from different threads.
221+
222+
If *mtstates.singleton()* is called with the same *name* parameter from
223+
concurrently running threads the second invocation waits until the first
224+
invocation is finished or fails.
201225

202-
* *id* - integer, the unique state id that can be obtained by
203-
*state:id()*.
204226

205-
* *name* - string, the optional name that was given when the
206-
state was created with *mtstates.newstate()*. To
207-
find a state by name the name must be unique for
208-
the whole process.
227+
* *name* - mandatory string, name for finding an existing state. If the state is
228+
not found the following parameters are used to create a new state
229+
with the given name.
209230

210-
Possible errors: *mtstates.error.ambiguous_name*,
211-
*mtstates.error.unknown_object*
231+
* *libs*, *setup*, *...* - same parameters as in [*mtstates.newstate()*](#newstate).
212232

233+
This function returns a state referencing lua object with *state:isowner() == true*.
213234

214-
* **Second form:**
215-
216-
Creates a lua object for referencing an existing state by name. If the
217-
state referenced by the name does not exist, a new state is created using
218-
the supplied parameters. This invocation can be used to atomically construct a
219-
globally named state securely from different threads.
220-
221-
* *name* - mandatory string, name for finding an existing state. If the state is
222-
not found the following parameters are used to create a new state
223-
with the given name.
224-
225-
* *libs*, *setup*, *...* - same parameters as in [*mtstates.newstate()*](#newstate).
235+
This function should only by used for very special use cases: Because references
236+
to the same *mtstates*'s state from different lua states are reference counted
237+
special care has to be taken that no reference cycle is constructed using
238+
*mtstates.singleton()*.
226239

227-
Possible errors: *mtstates.error.ambiguous_name*,
228-
*mtstates.error.invoking_state*,
229-
*mtstates.error.state_result*
240+
Possible errors: *mtstates.error.ambiguous_name*,
241+
*mtstates.error.invoking_state*,
242+
*mtstates.error.state_result*
230243

231244
* **`mtstates.id()`**
232245

233246
Gives the state id of the currently running state invoking this function.
234247
Returns `nil` if the current state was not constructed via
235-
*mtstates.newstate()* or the second form of *mtstates.state()*.
248+
*mtstates.newstate()* or *mtstates.singleton()*.
236249

237250

238251
* **`mtstates.type(arg)`**
@@ -279,8 +292,7 @@ assert(thread:join())
279292
Returns the results of the state callback function. Results can be simple data types
280293
(string, number, boolean, nil, light user data).
281294

282-
Possible errors: *mtstates.error.cycle_detected*
283-
*mtstates.error.interrupted*,
295+
Possible errors: *mtstates.error.interrupted*,
284296
*mtstates.error.invoking_state*,
285297
*mtstates.error.object_closed*,
286298
*mtstates.error.state_result*
@@ -322,11 +334,29 @@ assert(thread:join())
322334
at every operation again, if *false* the state is no
323335
longer interrupted.
324336

337+
338+
* **`state:isowner()`**
339+
340+
Returns `true` if the state referencing lua object owns the referenced
341+
state. If the last owning lua object is garbage collected the underlying
342+
state is closed.
343+
344+
State referencing objects constructed via *mtstates.newstate()* or
345+
*mtstates.singleton()* are owning the state.
346+
347+
State referencing objects constructed via *mtstates.state()* are
348+
not owning the state.
349+
350+
325351
* **`state:close()`**
326352

327353
Closes the underlying state and frees the memory. Every operation from any
328-
referencing object raises a *mtstates.error.object_closed*. A closed state
329-
cannot be reactivated.
354+
referencing object raises a *mtstates.error.object_closed*.
355+
356+
A closed state cannot be found via its name or id using the function
357+
*mtstates.state()*.
358+
359+
A closed state cannot be reactivated.
330360

331361
Possible errors: *mtstates.error.concurrent_access*
332362

@@ -363,13 +393,6 @@ assert(thread:join())
363393
Raised if *state:close()* is called while the state is processing a call
364394
on a parallel running thread.
365395

366-
* **`mtstates.error.cycle_detected`**
367-
368-
Raised if *state:call()* of a state is called while it is called by the same
369-
thread. This can only occur if one state gets somehow a reference to itself.
370-
It is strictly advised not to build cycle of states because this can lead
371-
to memory leaks.
372-
373396
* **`mtstates.error.interrupted`**
374397

375398
The state was interrupted by invoking the method *state:interrupt()*.
@@ -407,45 +430,31 @@ assert(thread:join())
407430
cannot be created because the object cannot be found by
408431
the given id or name.
409432

410-
All mtstates objects are subject to garbage collection and therefore a reference to a
411-
created object is needed to keep it alive, i.e. if you want to pass an object
412-
to another thread via name or id, a reference to this object should be kept in the
413-
thread that created the object, until the receiving thread signaled that a reference
414-
to the object has been constructed in the receiving thread, example:
433+
All mtstates objects are subject to garbage collection and therefore a owning
434+
reference to a created object is needed to keep it alive, example:
415435

416436
```lua
417-
local llthreads = require("llthreads2.ex")
418-
local mtmsg = require("mtmsg")
419437
local mtstates = require("mtstates")
420-
local threadIn = mtmsg.newbuffer()
421-
local threadOut = mtmsg.newbuffer()
422-
local state = mtstates.newstate("return function() end")
423-
local stateId = state:id()
424-
local thread = llthreads.new(function(inId, outId, stateId)
425-
local mtmsg = require("mtmsg")
426-
local mtstates = require("mtstates")
427-
local threadIn = mtmsg.buffer(inId)
428-
local threadOut = mtmsg.buffer(outId)
429-
local state = mtstates.state(stateId)
430-
assert(state:id() == stateId)
431-
threadOut:addmsg("started")
432-
assert(threadIn:nextmsg() == "exit")
433-
threadOut:addmsg("finished")
434-
end,
435-
threadIn:id(),
436-
threadOut:id(),
437-
stateId)
438-
-- state = nil -- not now!
439-
-- collectgarbage()
440-
thread:start()
441-
assert(threadOut:nextmsg() == "started")
442-
state = nil -- now it's safe
438+
local s1 = mtstates.newstate("return function() return 3 end")
439+
local id = s1:id()
440+
local s2 = mtstates.state(id)
441+
local s3 = mtstates.state(id)
442+
assert(s1:isowner() == true)
443+
assert(s2:isowner() == false)
444+
assert(s3:isowner() == false)
445+
s2 = nil
443446
collectgarbage()
444-
threadIn:addmsg("exit")
445-
assert(threadOut:nextmsg() == "finished")
446-
assert(thread:join())
447+
assert(s3:call() == 3)
448+
assert(mtstates.state(id):id() == id)
449+
s1 = nil
447450
collectgarbage()
448-
local _, err = pcall(function() mtstates.state(stateId) end)
451+
local _, err = pcall(function()
452+
s3:call()
453+
end)
454+
assert(err:match(mtstates.error.object_closed))
455+
local _, err = pcall(function()
456+
mtstates.state(id)
457+
end)
449458
assert(err:match(mtstates.error.unknown_object))
450459
```
451460

examples/example05.lua

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,22 @@
1-
local llthreads = require("llthreads2.ex")
2-
local mtmsg = require("mtmsg")
3-
local mtstates = require("mtstates")
4-
local threadIn = mtmsg.newbuffer()
5-
local threadOut = mtmsg.newbuffer()
6-
local state = mtstates.newstate("return function() end")
7-
local stateId = state:id()
8-
local thread = llthreads.new(function(inId, outId, stateId)
9-
local mtmsg = require("mtmsg")
10-
local mtstates = require("mtstates")
11-
local threadIn = mtmsg.buffer(inId)
12-
local threadOut = mtmsg.buffer(outId)
13-
local state = mtstates.state(stateId)
14-
assert(state:id() == stateId)
15-
threadOut:addmsg("started")
16-
assert(threadIn:nextmsg() == "exit")
17-
threadOut:addmsg("finished")
18-
end,
19-
threadIn:id(),
20-
threadOut:id(),
21-
stateId)
22-
-- state = nil -- not now!
23-
-- collectgarbage()
24-
thread:start()
25-
assert(threadOut:nextmsg() == "started")
26-
state = nil -- now it's safe
27-
collectgarbage()
28-
threadIn:addmsg("exit")
29-
assert(threadOut:nextmsg() == "finished")
30-
assert(thread:join())
31-
collectgarbage()
32-
local _, err = pcall(function() mtstates.state(stateId) end)
33-
assert(err:match(mtstates.error.unknown_object))
1+
local mtstates = require("mtstates")
2+
local s1 = mtstates.newstate("return function() return 3 end")
3+
local id = s1:id()
4+
local s2 = mtstates.state(id)
5+
local s3 = mtstates.state(id)
6+
assert(s1:isowner() == true)
7+
assert(s2:isowner() == false)
8+
assert(s3:isowner() == false)
9+
s2 = nil
10+
collectgarbage()
11+
assert(s3:call() == 3)
12+
assert(mtstates.state(id):id() == id)
13+
s1 = nil
14+
collectgarbage()
15+
local _, err = pcall(function()
16+
s3:call()
17+
end)
18+
assert(err:match(mtstates.error.object_closed))
19+
local _, err = pcall(function()
20+
mtstates.state(id)
21+
end)
22+
assert(err:match(mtstates.error.unknown_object))

src/error.c

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ static const char* const MTSTATES_ERROR_INVOKING_STATE = "invoking_state";
1010
static const char* const MTSTATES_ERROR_INTERRUPTED = "interrupted";
1111
static const char* const MTSTATES_ERROR_STATE_RESULT = "state_result";
1212
static const char* const MTSTATES_ERROR_OUT_OF_MEMORY = "out_of_memory";
13-
static const char* const MTSTATES_ERROR_CYCLE_DETECTED = "cycle_detected";
1413

1514

1615
static void pushErrorMessage(lua_State* L, const char* name, int details)
@@ -105,11 +104,6 @@ int mtstates_ERROR_STATE_RESULT(lua_State* L, const char* stateString, const cha
105104
return lua_error(L);
106105
}
107106

108-
int mtstates_ERROR_CYCLE_DETECTED(lua_State* L)
109-
{
110-
return throwError(L, MTSTATES_ERROR_CYCLE_DETECTED);
111-
}
112-
113107
int mtstates_ERROR_OUT_OF_MEMORY(lua_State* L)
114108
{
115109
return throwError(L, MTSTATES_ERROR_OUT_OF_MEMORY);
@@ -141,7 +135,6 @@ int mtstates_error_init_module(lua_State* L, int errorModule)
141135
publishError(L, errorModule, MTSTATES_ERROR_INTERRUPTED);
142136
publishError(L, errorModule, MTSTATES_ERROR_INVOKING_STATE);
143137
publishError(L, errorModule, MTSTATES_ERROR_STATE_RESULT);
144-
publishError(L, errorModule, MTSTATES_ERROR_CYCLE_DETECTED);
145138
publishError(L, errorModule, MTSTATES_ERROR_OUT_OF_MEMORY);
146139

147140
return 0;

src/error.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ int mtstates_ERROR_STATE_RESULT(lua_State* L, const char* stateString, const cha
1919

2020
void mtstates_push_ERROR_STATE_RESULT(lua_State* L, const char* stateString, const char* errorDetails);
2121

22-
int mtstates_ERROR_CYCLE_DETECTED(lua_State* L);
23-
2422
int mtstates_ERROR_OUT_OF_MEMORY(lua_State* L);
2523
int mtstates_ERROR_OUT_OF_MEMORY_bytes(lua_State* L, size_t bytes);
2624

0 commit comments

Comments
 (0)