lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On 17.06.20 23:41, Andrea wrote:
> For *mtmsg*:
> - What is the purpose of listeners?
> so I guess one buffer could really do the job, or not?

Yes you are right, in principle everything can be done with buffer objects
without the need to use listener objects. Your feedback is very welcome and it
inspires me to rethink the architecture of mtmsg.

As far as I remember the initial motivation for the listener was to reduce
overall locking time (every invocation of addmsg/setmsg/nextmsg locks the buffer
object for short time). But actually this is not the case, all buffer objects
connected to the same listener are sharing the listener's mutex (see below for
further thougts about this topic).

The purpose for the listener is to be able to wait at the same time for two kind
of buffer objects: a buffer where messages are appended (using buffer:addmsg())
and one or more buffers where old messages are replaced (using buffer:setmsg()).

Example for buffer:setmsg(): consider an audio thread that sets the current
volume with buffer:setmsg(). The GUI thread reads the current volume from the
buffer and displays a volume meter. Old volume values are not needed in this use
case. If you want to have more than one buffer of this kind (e.g. more than one
volume values distributed in several audio threads) or if you want to mix it
with another buffer that is filled using buffer:addmsg() for messages that
should not be discarded than you could use a listener to listen to all of these
buffers at the same time.

> - I found a minor spelling error in "modes *acessing *the same buffer."

Thanks for reporting, I always have to think again about the writing of
"accessing" ;-)

> understand the meaning of that note. Blocking/non-Blocking is not a property of
> the buffer, but it defines the behavior of the function that reads the buffer,
> correct?

To be more precise: buffer objects are living outside the Lua world. In the Lua
world you have buffer referencing objects. The Blocking/NonBlocking flag is
stored in the buffer referencing object and is evaluated  and considered if you
are invoking a method on the buffer referencing object. Other buffer referencing
objects (which could be in other threads) are having their own blocking flag
even if they are referencing the same underlying buffer.

> - abort: it is not clear to me what this does and why the abort operation can be
> canceled; what happens when abort is issued?

The functions mtmsg.abort() / buffer:abort() / listener:abort() can be used to
interrupt/cancel operations from other threads. These methods are setting an
abortion flag (gloabally / in the buffer / in the listener) which leads to
"mtmsg.error.operation_aborted" errors on most of the methods of the module /
buffer / listener (more details are in the documentation: at every function it
is remarked if this function could raise an operation_aborted error).

For example: if a thread is waiting in buffer:nextmsg() another thread can
invoke buffer:abort() and the call to buffer:nextmsg() ends with the error
"mtmsg.error.operation_aborted". You could of course also send a special message
which then has be interpreted by the to be interrupted thread as abortion. But
if the abortion is only an exception (i.e. is not normal case) then using abort
is easier. So if you detect an error in the main thread and want to cancel the
operation of a worker thread you could invoke abort() on the buffer the thread
is using and then every operation on this buffer raises an operation_aborted
error until you invoke buffer:abort(false). You could also combine this with
"mtint" for interrupting operations of the Lua VM of this thread. Then if the
thread has ended the main thread can call buffer:abort(false) and the buffer can
be used normally again without operation_aborted errors.

BTW: above example also illustrates another use case for listener objects: if
you have several worker threads and each thread is writing to it's own buffer
you can invoke abort() only on the buffer of the thread you want to interrupt.
You then need a listener object if you want to read from all the worker thread
buffers efficiently.

> - references: it is weird that the listener does not keep a reference to the
> buffers it is listening to, why this is so?

I'm not shure what the original reason for this was, perhaps it was the easiest
solution. In fact all buffers that are connected to the same listener are
referencing this listener behind the scene. So the underlying listener object
can only be destructed if the last buffer is destructed.

> to the buffer ends, then there will be no reference from the writer, and the
> reader/listener should count as a reference, if not it may happen that the
> buffers are garbage collected and then what happens to the listener?

The listener just works normally. The unread messages of the destructed buffer
are discarded.

Currently, if you want to receive the last message a thread is writing to an
buffer you have to keep a buffer referencing object until you have realized that
the thread has finished.

I'm going to rethink the current solution since I now have the impression that
this could be improved (but I'm not sure). Again thank you for your feedback!


> - only simple objects can be put into the buffer, no tables right? but one can
> send a string with the table construction which can then be executed from the
> receiver, right? this may be worth mentioning in the documentation...

hmm the documentation cleary says that you can send and receive a string. mtmsg
by intention does not serialize tables. There are several libraries that can
serialize tables.

I'm thinking about extending mtmsg by the following functionality: to reduce
locking time and to be able to efficiently write large messages, "writer" and
"reader" objects could be useful. A writer object then could be used to
incrementally build up a larger message without locking. Then if the message is
complete it could be send with one invocation and very short locking time. The
writer object manages it's own memory and therefore the number of mallocs are
reduced (once the writer has the right memory size, no more mallocs are needed).
The reader object would serve the same purpose for the other direction: you
could read a larger message from a buffer into the reader with short locking
time and then incrementally get all the Lua values out of the reader without
locking.

> For *mtstates*:
> - What is the purpose of mtstates.singleton? it seems to behaves like
> mtstates.newstate when state does not exist, otherwise it behaves like
> mtstates.state returning an id;

No, it's different:

- mtstates.newstate() always returns a state referencing object to a *new* state
even if there is already a state with the same name. So it's valid to have more
than one state with the same name. But then you cannot retrieve the state by
name using mtstates.state(). As the documentation says an "ambiguous_name" error
is raised in these cases.

- mtstates.singleton() returns a state referencing object to a new state if the
state by this name does not exist and returns a state referencing object to an
existing state if the state by this name does exist.

> can you make an example on when singleton should be used?

Hmm normally singletons should be avoided ;-) But if you want to have a
singleton state then you could use mtstates.singleton(). E.g. if you are having
some code in a larger framework or whatsoever where you cannot (or do not want
to) control the setup of everything and your code runs in different threads but
you want to have shared data/state you could use mtstates.singleton() from these
threads to access the shared state without having to create the singleton in the
main thread and without worrying how to initially create this state without race
conditions between the threads.

> but I am not able to fully understand the note
> on the care to be taken to ensure reference counting is correct; why this does
> not apply to mtstates.newstate? for both methods, singleton and newstate, the
> isowner property would be true...

mtstates.newstate() gives a state referencing object that owns the mtstate
object. Another system thread or mtstate object (more precisley: Lua main state)
can only access this mtstate object by using mtstates.state() using the state's
name or id.

mtstates.state() always gives a state referencing object that does not own the
state (i.e. a weak reference).

With this solution reference cycles are not possible. But you have to keep the
state owning reference object until you are sure that you don't need this state
in other threads. Otherwise an invocation of e.g. state:call() in another thread
would lead to an "object_closed" error if all owning reference objects have been
garbage collected.

mtstates.singleton() always gives a state referencing object that does own the
state. With this you can build reference cycles. For example: a singleton state
is constructed by the invocation of mtstates.singleton() with the name "foo".
Then within this singleton state mtstates.singleton() is invoked with the name
"foo". After this the singleton state will have a owning state referencing
object to itself and this is a reference cycle which could only be garbage
collected if the singleton state forgets the state referencing object to itself.
But overall this works and does not lead to an error, it just could lead to a
situation where mtstate objects are not destructed although they are not
reachable from outside. So this also shows that the usage of singletons is
dangerous ;-)

> - states can be interrupted... therefore the role of mtint is not clear (at
> least to me - and I am sorry for that) can one contrast the interrupt operation
> here with the interrupt operation in mtint package?

Ah I forgot this: I implemented mtstate.interrupt before I created mtint. So
there is a small overlap in functionality between both packages... However it
makes the usage easier. If you are using mtint for interruption then you need to
get the interruptible id of the created state. For this the created state has to
invoke mtint.id() to get the interruptible id of itself and then has to pass the
id to the thread that wants to interrupt the state. On the other hand mtint
provides more functionality since it lets you install an interrupt handling
function within the state. So for simple interruption the usage of
mtstates.interrupt() is easier whereas mtint provides more functionality.

Best regards,
Oliver