lua-users home
lua-l archive

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


On 24/06/2020 13:40, Francisco Olarte wrote:
Lorenzo.

On Wed, Jun 24, 2020 at 1:02 PM Lorenzo Donati
<lorenzodonatibz@tiscali.it> wrote:
On 24/06/2020 03:43, Gé Weijers wrote:
...
The behavior of os.exit is in my opinion a bit inconsistent, it
unwinds the stack and closes to-be-closed variables in the main
thread, but only if you call it from that main thread, and it
does not do anything to other coroutines.
I wonder whether the whole mechanism is amenable of improvement in
these areas (i.e. guaranteed closing despite any mechanism that
makes a program terminate in orderly fashion) or there is a
fundamental flaw in the design that makes it impossible to handle
some specific cases.

You must not forget os.exit calls exit, and exit is a big hammer.

You do not normally call exit(3) on a heavily threaded program or
from deep within a call stack, unless you want problems, it is
normally done as a civilized abort.


I'm no big expert of multithreading, either cooperative or preemptive,
and I use C only to a limited extent.

However I thought C's `exit` was the right way to exit a program
(possibly together with `atexit` mechanism) if returning from main is
too cumbersome.

At least C's standard seems to think so:
file:///G:/root/main/core/C-CPP/doc/standards/cppreference.com/en/c/program/exit.html

I admit I've no experience with C big programs (say 5kLOC+; especially
multithreaded/GUI ones), so I don't know what's current practice.



Although I've always wanted a clean, language-supported way to do
RAII in Lua, I'm still quite unsure if the current mechanism, as it
is now, is worth the hassle.

Look at the RAII poster child. C++. If you call os.exit it makes
some cleanups, but does not destroy automatic storage duration
objects ( which IMHO are the most similar to lua toclose ones ). It
closes some things, destroys automatics ( which may be similar to lua
__gc on globals, which IIRC is invoked ), and goes out.

You are right! I just checked here:

file:///G:/root/main/core/C-CPP/doc/standards/cppreference.com/en/cpp/utility/program/exit.html

In C++ std::exit doesn't unwind the stack. I didn't know that!

About year 2000 I had been using C++ intensively for about 3 years  and
never stumbled on that info. Mostly was number-crunching, data analysis
and offline simulation of TLC systems, so no interactive stuff.


And toclose is not RAII, is try-with-resources, which AFAIK is what
is normally done to approach RAII in garbage-collected languages,
when realizing not all resources are as easy as memory.


I don't follow you here. Why do you say it is not RAII? To me RAIIin C++ is when you create a local object whose ctor allocates resources and
whose destructor deallocates them when the object goes out of scope or
the stack unwinds (returns, exceptions), thanks to C++ semantics.

Apart from not there being an exact parallel, because of different
mechanisms and semantics, it seems Lua TBC vars are just a way to
conceptually do RAII, i.e. deterministic automatic release of resources.

The key point being the resource release handler (__close metamethod) is
defined upfront when you acquire the resource, and not lexically scoped
as a catch block at the end.

Am I missing something about what you are saying?


I didn't follow the 5.4 development closely, but it seems that the
mechanism doesn't provide unique features. I mean, you can still do
RAII without it, it just needs more programming discipline.

You can approach RAII. And you can perfectly manage resources in
assembler, C++ compilers do it. But the point on RAII is freeing you
from too much programming discipline, insuring that
"do_database_stuff(create_database_connection())" and similar stuff
just work. If you try to do it by adding a destroy() method to every
"object" you use and carefully using pcall anywhere you can get an
error, and adding destroy() calls everywhere,  you do not gain much
clarity, it's better to seek other ways.

I really do not use os.exit, all my lua is embeded, but it should
not be to difficult to dessign your objects in a way that they are
destroyed ( just overrwrite os.exit, add some atexit() support, do
something like having and idempotent destroy method, make your
objects register in a table for atexit, make __close call destroy
and deregister them, maybe use some handle idiom for easier
clearing, easy-peasy).

The whole point of the mechanism, as I see it, is that you could do
RAII in a much simpler way. But if it introduces other, subtler,
issues then I'm beginning to question its usefulness.

Use it like try-with-resources, for things which need to be cleaned
when the program errors and unwinds, keep using resources which need
to be properly cleaned on os.exit in other way, it doesn't have to
solve all the problems, and it manages to solve some of them.

To be clearer: now (Lua 5.3) I can do RAII using more discipline
and sprinkling error handling code in key parts of the code. This
approach adds clutter to the code but the error handling code is
explicit and clear.

That is because it is not RAII, the point of RAII is AVOIDING the
sprinkling.


You are right. My bad. I didn't mean to write RAII. I used RAII here to
mean "correct release of resources even on error conditions".

In 5.4 I could reduce clutter by using TBC vars, but then I must
add some extra checks or mechanism to handle those exceptional
cases when TBC vars mechanism "fails" (and coroutine users are
especially worse off, it seems). Moreover, the new syntax is not
that great.

You do not need to add. Move what you want to TBC, keep the rest as
is.

All in all, I'm starting to think that the mechanism is not
well-polished yet. At least not enough to warrant a shift of
paradigm. But maybe I'm missing something.

IMNSHO, you'll never be able to have RAII in a language like lua,
Java, etc.. where ( ignoring lua strings, number, ... ) everything
is a pointer in disguise and you cannot have value objects and it is
relying on garbage collection [...]

Again, here I lost you. What's the problem about objects not being on
the stack but being references? TBC vars have been created just for
that. I agree the semantics is not the same as C++, but the general idea
is the same:

1. (C++): Allocate a local object X of a class with a CTOR that
allocates the resources you need.
1. (Lua): Create a local TB vars that refers to an object X with a
__close metamethod that allocates the resource you need.


2. (C++): use the resources accessing them through helper methods of X.
2. (Lua): use the resources accessing them through helper methods of X.

3. (C++): when you exit the scope where the resources are used using X,
stack is unwound, and X destructor is called, which correctly releases
the resources.
3. (Lua): when you exit the scope where the resources are used using X,
X's __close metamethod is called, which correctly releases the resources.

The difference is that C++ keeps track at compile time of which
destructors have to be called for object on the stack. Lua keeps track
at runtime of which stack slot are associated with TBC vars and calls
the __close metamethod of the object the var is referring to, which
can't be changed since they are <const>. But the pattern is the same.
The key poing being /deterministic/ cleanup instead of relying on
finalizers, which are executed "randomly" under GC control.

With TBC vars GC doesn't come into play, if I got it right.
Am I missing something?


I've found garbage collection is typically great when you must do
complex memory only structures, not that much elsewhere. I've till
now been fortunate to be able to put this kind of things in arenas
when I have to use them in C++, so no need to use Boehms or similar
things. But for "heavy" resources, I rely on RAII, potentially with
shared_ptrs, and I never, never, use std::exit from deep down, I
just raise my exit_exception and catch it in main().

Francisco Olarte.


-- Lorenzo