lua-users home
lua-l archive

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


On 24/06/2020 20:09, Francisco Olarte wrote:
Lorenzo:

On Wed, Jun 24, 2020 at 5:29 PM Lorenzo Donati
<lorenzodonatibz@tiscali.it> wrote:
...
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.

Well, C exit jumps out of the call stack and kills your process.
stdlib manages to have your FILE * flushed, OS frees your mem and
handles. But if you need to do cleanup things like status saving, you
need atexit. You use that when returning from main is cumbersome, but
normally you have some other methods ( like killing an event loop or
some thing like that )

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

You notice this is a local reference ( windowish? ) to a file in your
machine? ( I now cpp reference tells that, and know it is a somehow
non leaking way to exit, but in complex programas your transactions
will not get commited and things like dat ).


(*doh*)! I just had two browser tabs open (together with a dozen more), one with a local copy of cppreference.com and the other with the online site. It seems I copied the address from the wrong tab. Sorry!
For the archive, the correct link was meant to be:

https://en.cppreference.com/w/c/program/exit


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.

It depends on the libs you use, many give you some ways for easier
exiting, or register their own cleanup handlers, but exit is really
hard.


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!


Again, copied from wrong window. Correct link was meant to be:

https://en.cppreference.com/w/cpp/utility/program/exit

exit never unwinds the stack its purpose is exactly exiting without unwinding.

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.

Those are generally easy on the resource side, I worked on those
things a lot and you normally could even abort.

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.

RAII in C++ means the moment a constructor get executed the destructor
will be called unless you've taken the precaution of using new.

You do not have to store the var in an especial construct, you can
also create it in a temporary expression, i.e., in C++ you can do
something like

db_connection(params).execute_query(my_query).get_resultset().print();

And the intermediate db_connection, query, resultset objects, if
appropiately managed, will vanish and be properly closed ( for these
things you need move semantics or advanced classes, in old school C++
you would propably have something like

resultset(query(connection(params),my_query)).print()


OK. That's also what I know. As long as an object is on the stack, the dtor will be called and manage resources tear down.


The thing is you do not have special toclose because you have value
objects, while in java/lua all your objects go into the free store,
you always use somethign like new. So you need syntax to manage them (
try/finally is not a language feature, it is a neccessity, java needs
that, C++ does not. Try with resources is try-finally on steroids. Lua
to-close is very similar to Java try with resources )

In C++ you can create a db connection or get a pointer to an allocated
one. How do you it dictates your management style. In Java or Lua you
always get a pointer, so you need toclose and try with resources if
you want local lifetime management.

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.

They are a safer and more elegant way to do a try-finally.

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.

That's just sugar, but yes, and sugar is important.

Am I missing something about what you are saying?

Once you have a destruction and an initialization method, call the
dtor __gc, __close, close, destroy or ~object, you can strictly say
you are doing RAII.

But using RAII in languages without proper scope tracking of values is
a PITA. You get help with TWR, toclose, but you do not get the same
easy code as you have with C++. In C++ you have to explicitly ask for
a dynamically allocated object via new ( or indirectly ) to be able to
foget destroying it. In Java/lua you have to explicitly mark it via
toclose or rely on the garbage collector with its problems.

And the problem with exit is orthogonal to this. It's Thor's retun hammer.

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".

That's correct code. It can be done in nearly any language (some of
them bomb out, but they are rare)
....
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.

C++ - declare the var, do not use new.
Lua - remember to use a to close.

What happens if you want the resource holding object just to pass it
to a method?


Now I think I'm getting your point (I hope).

The problem you seem to point out is resource ownership management. C++ objects, especially in newer versions with move semantics, can own resources and be passed around if needed. So the lifetime of a resource is linked to the lifetime of a specific object. If that object is on the stack, exiting the scope of that object will cause its deterministic destruction, and consequent automatic deterministic resource release.

In Lua, on the contrary, __toclose will be called only if the object (the resource handler, we may say) is stored in a TBC variable, so the cleanup is not linked to the lifetime of the object, but the scope of the TBC vars.

This precludes passing resource handlers around AND having deterministic cleanup, since passing them around will create aliased references to the handlers, so the risk of __close being called more than one time (whereas an object dtor in C++ cannot be called automatically more than one time if the object is on the stack). I can somewhat see you can probably get around some of these problems by making __close keep track of how many times it has been called, and maybe interact with __gc.

Mmmh. I now understand I can't get the whole picture with this __toclose feature. I need to see more examples on how it could be used besides the trivial case:

do
   local h <toclose> = allocate_resource(...)
   ...
   do_stuff_with_resource(h)
   ...
end -- here __toclose for h will be called and cleanup resource

I have some difficulties now to picture in my head more difficult scenarios. Besides missing deterministic execution, __gc seems far more easy to reason about.

C++ - create it in the parm. list as you would for a var.
Lua - Create a to close, and potentially a block, for the sole purpose
of holding the reg around the call.

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

No, you can do RAII in any language, it's just painful in some of
them, because you need to explicitly track them.
You cannot put <toclose> in a parameter list.
Also, you have no easy way to see how to track ownership ( i.e.,
imagine you need something fro share ownership. It can be done easily
passing shared__ptrs by value in C++, but althoguh shared_ptrs can be
easily done in lua via some ref-counting or linking and creative
__index manipulation they soon become cumbersome. You can get cleaner
code with toclose, but it is very limited because yo have to mark
every ownership point with local <toclose>.

The problem is I associate RAII with proper value objects, and I mix
it a bit. Nearly everything which you can do in C++ can be done with
creative use of toclose and scopes, but it will get complicated. I.e.,
to do something like filecopy(ofstream("dest"),ofstream("source"))"
you'll need to add a couple of local-toclose for the temporaries, plus
a do-end to avoid namespace pollution. It's not just variable lifetime
what the C++ compiler tracks. But you're right, something like FILE*
=fopen, very similar to lua io, is RAII too. The problem is I normally
want proper value lifetime tracking, which is hard to do without
values.

Francisco Olarte.


Thanks for the explanation!

-- Lorenzo