Exit one Lua state from a (C) API function

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
28 messages Options
12
bel
Reply | Threaded
Open this post in threaded view
|

Exit one Lua state from a (C) API function

bel

I need to create a Lua API function to close/exit a Lua state from a Lua script.
While “os.exit” will exit the entire host application, I just want to “exit” one (of multiple) Lua states and return to the “lua_pcall” in C, while the application (and the other states) keep running.

Something like this:

my_C_function()
    lua_newstate
    lua_pcall -->
        Some Lua code
        Lua pcall -->
            more Lua code
            call "state exit" function -------------|
            Lua code that should not be executed    |
        More Lua code that should not be executed   |
   
[continue here]  <------------------------------|
    lua_close


It cannot be just a luaL_error call, since this will be caught by a pcall in the Lua code.

Is there some recommended method to do this?
It should work with Lua 5.2, 5.3 and 5.4 and use only C and Lua (no C++, no operating system dependent code or CPU dependent code like asm).

I could try to build some setjmp/longjmp code around it, but I’m wondering if there might be some built-in feature, e.g., some Lua error/exceptions that can only be handled in C but not in Lua? In my case, I will call "lua_close" immediately after and I don't need to resume any operation in this state. But for curiosity, is there a way to keep the state "fully operational" after such an operation?


Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Gé Weijers

On Thu, Feb 25, 2021 at 12:58 PM bel <[hidden email]> wrote:

I need to create a Lua API function to close/exit a Lua state from a Lua script.I could try to build some setjmp/longjmp code around it, but I’m wondering if there might be some built-in feature, e.g., some Lua error/exceptions that can only be handled in C but not in Lua? In my case, I will call "lua_close" immediately after and I don't need to resume any operation in this state. But for curiosity, is there a way to keep the state "fully operational" after such an operation?


I think you're on the right track, I would define a function that first calls lua_close to release all the resources used by the state and the state itself, and then uses a longjmp to unwind the stack. I would unwind the stack AFTER calling lua_close. lua_close will free the lua_State object, so you'll have to create a new one.

The alternative would be to mess with the internals of the interpreter, which is likely to break when updating to a new version. One problem you have is that the pcall mechanism builds a linked list of "struct lua_longjmp" objects in the C runtime stack (see luaD_rawrunprotected in ldo.c), and if you longjmp around that code this list will not be unwound, so reusing the state is going to require some cleanup. I'm sure there's more.

Depending on your needs you could start your code in a coroutine, and use a coroutine yield from this coroutine to signal your code's exit. You can then use lua_resetthread to clean up the coroutine and reuse the state.


--

Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Viacheslav Usov
In reply to this post by bel
On Thu, Feb 25, 2021 at 9:58 PM bel <[hidden email]> wrote:

> Is there some recommended method to do this?

Unless you control tightly all the "C" [1] code, including all the
libraries, that interacts with a given state, that is generally
impossible. Because "C" code will generally expect that no transfer of
control can happen across lua_pcall (and the like) boundaries.
Whatever you do, you will either have to violate the expectation, with
generally unpredictable consequences, or have to be limited by it.

[1] "C" meaning whatever uses the Lua "C" API, which is not
necessarily in the C language.

Cheers,
V.
bel
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

bel

Creating a solution with setjmp/longjmp was not too hard (code: see below).
But I wonder, what could it really break?

It seems it does not leave a memory leak after lua_close (tested with clang address sanitizer), I suppose all user defined garbage collecting functions are called as well.

For a C library function using lua_pcall or lua_yield, I do not violate any valid expectation. A Lua library function calling lua_pcall cannot be sure the lua_pcall function will return, since the Lua code could also switch to another coroutine. For lua_yield, it is even stated in the manual “only use as a tail call”. Unless I have some unknown error in this reasoning, I do not introduce any additional problem.

For a C library using lua_pcallk or lua_yieldk, I was not sure at first – but I found there is no guarantee that a continuation function will ever be called. So, this should not cause any new problem as well.

Did I miss something?


(In case someone is interested, this is how I realized the "my_exit" function, plus some tests, but without complete error handling.)

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include <setjmp.h>
#include <stdio.h>
#include <assert.h>

jmp_buf JUMP_TO_EXIT;

int my_exit(lua_State *L)
{
  int num = lua_tointeger(L, 1);
  lua_close(L);
  longjmp(JUMP_TO_EXIT, num);
  return 0; /* will not return */
}

int fail_continuation_function(lua_State *L, int status, lua_KContext ctx) {
  assert(0); /* will never be called */
}

int my_pcall(lua_State *L)
{
  lua_pcallk(L, lua_gettop(L)-1, 0, 0, 0, fail_continuation_function);
  return 0;
}

int num_from_lua(const char *code)
{
  lua_State *L = luaL_newstate();  /* create state */
  luaL_openlibs(L);
  lua_pushcfunction(L, my_exit);
  lua_setglobal(L, "my_exit");
  lua_pushcfunction(L, my_pcall);
  lua_setglobal(L, "my_pcall");

  luaL_loadstring(L, code);
  int result = setjmp(JUMP_TO_EXIT);
  if (result) {
      return result;
  }
  int status = lua_pcall(L, 0, 1, 0);
  if (status) {
    printf("%s\n", lua_tostring(L, 1));
  } else {
    result = lua_tonumber(L, -1);
  }
  lua_close(L);
  return result;
}

int main()
{
  int a = num_from_lua("pcall(function () pcall(my_exit, 123) end); return 45;");
  assert(a == 123);

  a = num_from_lua("co = coroutine.create(function () pcall(my_exit, 67) end); coroutine.resume(co); return 89;");
  assert(a == 67);

  a = num_from_lua("co = coroutine.create(function () my_pcall(my_exit, 98) end); coroutine.resume(co); return 76;");
  assert(a == 98);

  a = num_from_lua("co = coroutine.create(function () my_pcall(coroutine.yield) end); coroutine.resume(co); return 54;");
  assert(a == 54);

  return 0;
}




On Fri, Feb 26, 2021 at 10:43 AM Viacheslav Usov <[hidden email]> wrote:
On Thu, Feb 25, 2021 at 9:58 PM bel <[hidden email]> wrote:

> Is there some recommended method to do this?

Unless you control tightly all the "C" [1] code, including all the
libraries, that interacts with a given state, that is generally
impossible. Because "C" code will generally expect that no transfer of
control can happen across lua_pcall (and the like) boundaries.
Whatever you do, you will either have to violate the expectation, with
generally unpredictable consequences, or have to be limited by it.

[1] "C" meaning whatever uses the Lua "C" API, which is not
necessarily in the C language.

Cheers,
V.
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Andrew Gierth
>>>>> "bel" == bel  <[hidden email]> writes:

 bel>   int result = setjmp(JUMP_TO_EXIT);
 bel>   if (result) {
 bel>       return result;
 bel>   }

Nitpick: this isn't actually legal code. A setjmp() call is allowed by
the specification only in certain highly restricted contexts (the entire
controlling expression of an if () or select (), or compared against an
integer constant or with ! in such an expression, or as a standalone
expression statement); any other use, including assigning the result to
a variable, is undefined.

This does mean that you can't use the return value from setjmp to convey
much information beyond the bare fact that longjmp was called; in
general you shouldn't try.

--
Andrew.
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Mimmo Mane
In reply to this post by bel
On Thu, Feb 25, 2021 at 9:58 PM bel wrote:
> It cannot be just a luaL_error call, since this will be caught by a pcall in the Lua code.

Could a pcall wrapper be a good solution for you? Something like (not tested):

```
local propagate = {}
local oldpcall = pcall
pcall = function(f)

  local res = table.pack(oldpcall(f)) -- [1]

  -- If the "Propagate" request is found, it will forward the error to
the upper level
  if not res[1] and res[2] == propagate then
    error(res[2])
  end

  -- Otherwise, it behaves like the standard pcall
  return table.unpack(res)

end
```

then, a exit_to_c function can be defined as

```
local function exit_to_c()
  error(propagate)
end
```

(probably you need to wrap xpcall too)

Mimmo Mane

[1] Pack/unpack can be avoided with something like
local function result_check(f, ...)
    return (function(a, ...)
    if a == 'default' then
      return 'default', 1, 2, 3
    end
    return a, ...
  end)(f(...))
end
print(result_check(function() return 'foo','bar' end))
print(result_check(function() return 'default','bar' end))
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Viacheslav Usov
In reply to this post by bel
On Sat, Feb 27, 2021 at 6:08 PM bel <[hidden email]> wrote:

> A Lua library function calling lua_pcall cannot be sure the lua_pcall function will return, since the Lua code could also switch to another coroutine.

The manual: "Lua raises an error whenever it tries to yield across an
API call, except for three functions: lua_yieldk, lua_callk, and
lua_pcallk" (beginning of section 4.5).

The whole point of lua_pcall() is that it always returns, except when
it executes, directly or indirectly, some misbehaving "C" code.

'Misbehaving' above is from Lua's perspective as officially
documented. You are entirely free to redefine that in your own
application, you just need to make sure that all the "C" code it uses
is compatible with your definition.

Cheers,
V.
bel
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

bel
> The whole point of lua_pcall() is that it always returns, except when
> it executes, directly or indirectly, some misbehaving "C" code.

It seems I can create a simple Lua code that does not return after pcall:

co = coroutine.create(function () my_pcall(coroutine.yield) end);
coroutine.resume(co);
print(coroutine.status(co)); -- dead ... but no exception or other issue

Corresponding C code for "my_pcall" - I would not think this is "misbehaved":

int my_pcall(lua_State *L)
{
  lua_pcall(L, lua_gettop(L)-1, 0, 0);
  assert(0);
  return 0;
}

Maybe Lua raised an error in coroutine.yield (where does this error go?).
But it still did not continue after lua_pcall - the assert is not triggered.

But this is exactly why I'm asking.
Is there a test code that would behave differently?
Will it really break some test code?

And if it is not possible to build something "around" Lua, I would need to build it "into" it:

> Could a pcall wrapper be a good solution for you? Something like (not tested):
> ...

If the solution with a setjmp/longjmp "around" Lua works without (unacceptable) side effects, I would not modify anything "inside" Lua.
I already thought about modifying pcall - not in Lua code but in C code. Either by creating my own pcall (and xcpall), or by modifying lua_pcallk in lapi.c if required.
Something like "if the message argument of error(message) is my userdata type, then re-throw this exception".

With an appropriate creation of "my userdata" objects, this could effectively introduce some kind of "typed exceptions" and "typed catch"..
But this would actually be much more than my requirement right now - something that I would rather deposit on a Lua 5.5 wish list (if there is something like this) - because introducing such a concept can do much more than I need now and seems a little like "using a sledgehammer to crack a nut".

Probably this "rethrow" concept with a simple convention of what to rethrow is a solution with less undesirable side effects and better in line with the architecture?


On Sun, Feb 28, 2021 at 3:40 PM Viacheslav Usov <[hidden email]> wrote:
On Sat, Feb 27, 2021 at 6:08 PM bel <[hidden email]> wrote:

> A Lua library function calling lua_pcall cannot be sure the lua_pcall function will return, since the Lua code could also switch to another coroutine.

The manual: "Lua raises an error whenever it tries to yield across an
API call, except for three functions: lua_yieldk, lua_callk, and
lua_pcallk" (beginning of section 4.5).

The whole point of lua_pcall() is that it always returns, except when
it executes, directly or indirectly, some misbehaving "C" code.

'Misbehaving' above is from Lua's perspective as officially
documented. You are entirely free to redefine that in your own
application, you just need to make sure that all the "C" code it uses
is compatible with your definition.

Cheers,
V.
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Viacheslav Usov
On Sun, Feb 28, 2021 at 10:37 PM bel <[hidden email]> wrote:

> It seems I can create a simple Lua code that does not return after pcall:
>
> co = coroutine.create(function () my_pcall(coroutine.yield) end);
> coroutine.resume(co);
> print(coroutine.status(co)); -- dead ... but no exception or other issue
>
> Corresponding C code for "my_pcall" - I would not think this is "misbehaved":
>
> int my_pcall(lua_State *L)
> {
>   lua_pcall(L, lua_gettop(L)-1, 0, 0);
>   assert(0);
>   return 0;
> }
>
> Maybe Lua raised an error in coroutine.yield (where does this error go?).
> But it still did not continue after lua_pcall - the assert is not triggered.

This is not what I experience using Lua 5.3. What I get is error
"attempt to yield across a C-call boundary" pushed on stack by
lua_pcall(), exactly like the manual states. Whether assert() is
"triggered" is another matter, check the NDEBUG define in your
toolchain.

Cheers,
V.
bel
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

bel
> This is not what I experience using Lua 5.3.

I hope this is not Lua version dependent (at least for Lua 5.2, 5.3 and 5.4 - I can ignore 5.1 and I don't care about 5.0 or older)

> check the NDEBUG define in your toolchain.

Actually the code I posted before was a little simplified, to keep the mail short.
I do not (only) use assert, but some makros and several printf - but I can post the full code + build command line (see below).
It does not trigger any error on Ubuntu Linux.

Build command line:
clang luaexit.c  -llua -lm -ldl -O1 -g -fsanitize=address -fno-omit-frame-pointer -Wall

Full source code:
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include <setjmp.h>
#include <stdio.h>
#include <assert.h>


jmp_buf JUMP_TO_EXIT;

#define UNREACHABLE {printf("Reached unreachable code at %u\n", __LINE__); assert(0);}
#define check(val, exp) check_func(val, exp, __LINE__)


int my_exit(lua_State *L)
{
  int num = lua_tointeger(L, 1);
  lua_close(L);
  longjmp(JUMP_TO_EXIT, num);
  UNREACHABLE
  return 0; /* will not return */
}


int fail_continuation_function(lua_State *L, int status, lua_KContext ctx) {
  printf("FAIL @ %u\n", __LINE__);
  UNREACHABLE
}


int my_pcallk(lua_State *L)
{
  lua_pcallk(L, lua_gettop(L)-1, 0, 0, 0, fail_continuation_function);
  UNREACHABLE
  return 0;
}


int my_pcall(lua_State *L)
{
  lua_pcall(L, lua_gettop(L)-1, 0, 0);
  UNREACHABLE
  return 0;
}


int num_from_lua(const char *code)
{
  lua_State *L = luaL_newstate();  /* create state */
  luaL_openlibs(L);
  lua_pushcfunction(L, my_exit);
  lua_setglobal(L, "my_exit");
  lua_pushcfunction(L, my_pcallk);
  lua_setglobal(L, "my_pcallk");

  luaL_loadstring(L, code);
  int result = setjmp(JUMP_TO_EXIT);
  if (result) {
      return result;
  }
  int status = lua_pcall(L, 0, 1, 0);
  if (status) {
    printf("%s\n", lua_tostring(L, 1));
  } else {
    result = lua_tonumber(L, -1);
  }
  lua_close(L);
  return result;
}


void check_func(int is, int exp, unsigned line)
{
    if (is == exp) {
        printf("passed line %u\n", line);
    } else {
        printf("ERROR in line %u: is = %i, expected = %i\n", line, is, exp);
// assert(0);
    }
}


int main(int argc, char *argv[])
{
  (void) argc;
  (void) argv;
 
  int a;

  a = num_from_lua(
    "print(_VERSION); "
    "return 10;"
  );
  check(a, 10);

  a = num_from_lua(
    "pcall("
    "  function () "
    "    pcall(my_exit, 123) "
    "  end"
    "); "
    "return 45;"
  );
  check(a, 123);

  a = num_from_lua(
    "co = coroutine.create("
    "  function () "
    "    pcall(my_exit, 67) "
    "  end"
    "); "
    "coroutine.resume(co); "
    "return 89;"
  );
  check(a, 67);

  a = num_from_lua(
    "co = coroutine.create("
    "  function () "
    "    my_pcallk(my_exit, 98) "
    "  end"
    "); "
    "coroutine.resume(co); "
    "return 76;"
  );
  check(a, 98);

  a = num_from_lua(
    "co = coroutine.create("
    "  function () "
    "    my_pcallk(coroutine.yield) "
    "  end"
    "); "
    "coroutine.resume(co); "
    "return 54;"
  );
  check(a, 54);

  a = num_from_lua(
    "co = coroutine.create("
    "  function () "
    "    my_pcall(coroutine.yield) "
    "  end"
    "); "
    "coroutine.resume(co); "
    "print(coroutine.status(co)); "
    "return 32;"
  );
  check(a, 32);

  printf("Test the test!\n");
  a = 1;
  check(a, 2); /* 1 != 2 */
 
  //UNREACHABLE --> will cause assert + core dump

  return 0;
}






I can p





On Mon, Mar 1, 2021 at 9:44 AM Viacheslav Usov <[hidden email]> wrote:
On Sun, Feb 28, 2021 at 10:37 PM bel <[hidden email]> wrote:

> It seems I can create a simple Lua code that does not return after pcall:
>
> co = coroutine.create(function () my_pcall(coroutine.yield) end);
> coroutine.resume(co);
> print(coroutine.status(co)); -- dead ... but no exception or other issue
>
> Corresponding C code for "my_pcall" - I would not think this is "misbehaved":
>
> int my_pcall(lua_State *L)
> {
>   lua_pcall(L, lua_gettop(L)-1, 0, 0);
>   assert(0);
>   return 0;
> }
>
> Maybe Lua raised an error in coroutine.yield (where does this error go?).
> But it still did not continue after lua_pcall - the assert is not triggered.

This is not what I experience using Lua 5.3. What I get is error
"attempt to yield across a C-call boundary" pushed on stack by
lua_pcall(), exactly like the manual states. Whether assert() is
"triggered" is another matter, check the NDEBUG define in your
toolchain.

Cheers,
V.
v
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

v
On Mon, 2021-03-01 at 19:54 +0100, bel wrote:
>
> #define UNREACHABLE {printf("Reached unreachable code at %u\n",
> __LINE__); assert(0);}

To avoid confusion with compiler cutting asserts away on release
builds, how about using abort() instead of assert(0)?
--
v <[hidden email]>
bel
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

bel
> To avoid confusion with compiler cutting asserts away on release
> builds, how about using abort() instead of assert(0)?

The compiler does not cut away printf.
This is the output I forgot to add in the mail 7 minutes ago:

Lua 5.4
passed line 94
passed line 104
passed line 115
passed line 126
passed line 137
dead
passed line 149
Test the test!
ERROR in line 153: is = 1, expected = 2


The final ERROR line tests that the test will trigger.
So I can be definitely sure, this is not a DEBUG vs. RELEASE or abort() vs. assert() issue.


On Mon, Mar 1, 2021 at 7:59 PM v <[hidden email]> wrote:
On Mon, 2021-03-01 at 19:54 +0100, bel wrote:
>
> #define UNREACHABLE {printf("Reached unreachable code at %u\n",
> __LINE__); assert(0);}

To avoid confusion with compiler cutting asserts away on release
builds, how about using abort() instead of assert(0)?
--
v <[hidden email]>
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Viacheslav Usov
In reply to this post by bel
On Mon, Mar 1, 2021 at 7:55 PM bel <[hidden email]> wrote:

[A]

> int my_pcall(lua_State *L)
> {
>   lua_pcall(L, lua_gettop(L)-1, 0, 0);
>   UNREACHABLE
>   return 0;
> }

[B]

> int num_from_lua(const char *code)
> {
>   lua_State *L = luaL_newstate();  /* create state */
>   luaL_openlibs(L);
>   lua_pushcfunction(L, my_exit);
>   lua_setglobal(L, "my_exit");
>   lua_pushcfunction(L, my_pcallk);
>   lua_setglobal(L, "my_pcallk");
>
>   luaL_loadstring(L, code);
>   int result = setjmp(JUMP_TO_EXIT);
>   if (result) {
>       return result;
>   }
>   int status = lua_pcall(L, 0, 1, 0);
>   if (status) {
>     printf("%s\n", lua_tostring(L, 1));
>   } else {
>     result = lua_tonumber(L, -1);
>   }
>   lua_close(L);
>   return result;
> }

[C]

>   a = num_from_lua(
>     "co = coroutine.create("
>     "  function () "
>     "    my_pcall(coroutine.yield) "
>     "  end"
>     "); "
>     "coroutine.resume(co); "
>     "print(coroutine.status(co)); "
>     "return 32;"
>   );
>   check(a, 32);

Apparently you believe that [C] would call my_pcall() as defined in [A].

Observe, however, that [B] does not make the latter available to the
Lua state. So the coroutine in [B] simply fails when trying to call a
nil value, and my_pcall() is never called at all.

I would like to point out that for whatever strange reason you seem
determined to say that lua_pcall() or Lua's reference manual have a
bug, you should submit a bug report that demonstrates just this one
bug, not a program doing all kinds of things, which is so confusing
that you ended up confusing yourself.

Cheers,
V.
bel
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

bel


> Observe, however, that [B] does not make the latter available to the
> Lua state. So the coroutine in [B] simply fails when trying to call a
> nil value, and my_pcall() is never called at all.

Thank you, I missed that indeed.
Registering "my_pcall" as well changes the behavior and the "no longer unreachable" code after pcall is called.

> I would like to point out that for whatever strange reason you seem
> determined to say that lua_pcall() or Lua's reference manual have a
> bug, ...

No, that is not what I said.
I'm trying to find a solution to a specific problem - either by building a solution around Lua
or by modifying the Lua kernel itself if there is no other solution.
The program should help to test out if a particular solution works or not.
Or by documenting the restrictions which might exclude using some existing libraries.


On Mon, Mar 1, 2021 at 11:50 PM Viacheslav Usov <[hidden email]> wrote:
On Mon, Mar 1, 2021 at 7:55 PM bel <[hidden email]> wrote:

[A]

> int my_pcall(lua_State *L)
> {
>   lua_pcall(L, lua_gettop(L)-1, 0, 0);
>   UNREACHABLE
>   return 0;
> }

[B]

> int num_from_lua(const char *code)
> {
>   lua_State *L = luaL_newstate();  /* create state */
>   luaL_openlibs(L);
>   lua_pushcfunction(L, my_exit);
>   lua_setglobal(L, "my_exit");
>   lua_pushcfunction(L, my_pcallk);
>   lua_setglobal(L, "my_pcallk");
>
>   luaL_loadstring(L, code);
>   int result = setjmp(JUMP_TO_EXIT);
>   if (result) {
>       return result;
>   }
>   int status = lua_pcall(L, 0, 1, 0);
>   if (status) {
>     printf("%s\n", lua_tostring(L, 1));
>   } else {
>     result = lua_tonumber(L, -1);
>   }
>   lua_close(L);
>   return result;
> }

[C]

>   a = num_from_lua(
>     "co = coroutine.create("
>     "  function () "
>     "    my_pcall(coroutine.yield) "
>     "  end"
>     "); "
>     "coroutine.resume(co); "
>     "print(coroutine.status(co)); "
>     "return 32;"
>   );
>   check(a, 32);

Apparently you believe that [C] would call my_pcall() as defined in [A].

Observe, however, that [B] does not make the latter available to the
Lua state. So the coroutine in [B] simply fails when trying to call a
nil value, and my_pcall() is never called at all.

I would like to point out that for whatever strange reason you seem
determined to say that lua_pcall() or Lua's reference manual have a
bug, you should submit a bug report that demonstrates just this one
bug, not a program doing all kinds of things, which is so confusing
that you ended up confusing yourself.

Cheers,
V.
bel
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

bel
Another acceptable solution might be to allow calling the my_exit function only in cases where yield is allowed and return false otherwise.
Probably this can be done with "lua_isyieldable"
But I'm open for all suggestions that will solve the problem.

On Tue, Mar 2, 2021 at 6:59 AM bel <[hidden email]> wrote:


> Observe, however, that [B] does not make the latter available to the
> Lua state. So the coroutine in [B] simply fails when trying to call a
> nil value, and my_pcall() is never called at all.

Thank you, I missed that indeed.
Registering "my_pcall" as well changes the behavior and the "no longer unreachable" code after pcall is called.

> I would like to point out that for whatever strange reason you seem
> determined to say that lua_pcall() or Lua's reference manual have a
> bug, ...

No, that is not what I said.
I'm trying to find a solution to a specific problem - either by building a solution around Lua
or by modifying the Lua kernel itself if there is no other solution.
The program should help to test out if a particular solution works or not.
Or by documenting the restrictions which might exclude using some existing libraries.


On Mon, Mar 1, 2021 at 11:50 PM Viacheslav Usov <[hidden email]> wrote:
On Mon, Mar 1, 2021 at 7:55 PM bel <[hidden email]> wrote:

[A]

> int my_pcall(lua_State *L)
> {
>   lua_pcall(L, lua_gettop(L)-1, 0, 0);
>   UNREACHABLE
>   return 0;
> }

[B]

> int num_from_lua(const char *code)
> {
>   lua_State *L = luaL_newstate();  /* create state */
>   luaL_openlibs(L);
>   lua_pushcfunction(L, my_exit);
>   lua_setglobal(L, "my_exit");
>   lua_pushcfunction(L, my_pcallk);
>   lua_setglobal(L, "my_pcallk");
>
>   luaL_loadstring(L, code);
>   int result = setjmp(JUMP_TO_EXIT);
>   if (result) {
>       return result;
>   }
>   int status = lua_pcall(L, 0, 1, 0);
>   if (status) {
>     printf("%s\n", lua_tostring(L, 1));
>   } else {
>     result = lua_tonumber(L, -1);
>   }
>   lua_close(L);
>   return result;
> }

[C]

>   a = num_from_lua(
>     "co = coroutine.create("
>     "  function () "
>     "    my_pcall(coroutine.yield) "
>     "  end"
>     "); "
>     "coroutine.resume(co); "
>     "print(coroutine.status(co)); "
>     "return 32;"
>   );
>   check(a, 32);

Apparently you believe that [C] would call my_pcall() as defined in [A].

Observe, however, that [B] does not make the latter available to the
Lua state. So the coroutine in [B] simply fails when trying to call a
nil value, and my_pcall() is never called at all.

I would like to point out that for whatever strange reason you seem
determined to say that lua_pcall() or Lua's reference manual have a
bug, you should submit a bug report that demonstrates just this one
bug, not a program doing all kinds of things, which is so confusing
that you ended up confusing yourself.

Cheers,
V.
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Sean Conner
In reply to this post by bel
It was thus said that the Great bel once stated:

> I need to create a Lua API function to close/exit a Lua state from a Lua
> script.
> While “os.exit” will exit the entire host application, I just want to
> “exit” one (of multiple) Lua states and return to the “lua_pcall” in C,
> while the application (and the other states) keep running.
>
> Something like this:
>
> my_C_function()
>     lua_newstate
>     lua_pcall -->
>         Some Lua code
>         Lua pcall -->
>             more Lua code
>             call "state exit" function -------------|
>             Lua code that should not be executed    |
>         More Lua code that should not be executed   |
>     [continue here]  <------------------------------|
>     lua_close

  It's hard to follow your code above.  So let's add some state to
it---namely some Lua states:

        my_C_function(L1)
                L2 = lua_newstate()
                lua_pcall(L? .. what lua state goes here?)

                -- skip rest of code above because of lack of
                -- information about which state we're in where
               
  There also may be some confusion.  There are a few functions that return a
new Lua state:

        lua_State *lua_newstate(lua_Alloc,void *);
        lua_State *luaL_newstate(void);
        lua_State *lua_tothread(lua_State *,int);
        lua_State *lua_newthread(lua_State *);

  The first two create a new Lua state, without reference to any other
state.  The next one, lua_tothread() is like lua_tointeger() or
lua_tostring(), returning the value from a Lua stack index.  The last one at
confused me when I was first starting out.  It returns a new Lua state L2
that is related to the given Lua state L---they both share the same global
space.  Is this perhaps what the issue is?

  Even if you are creating a separate Lua state (separate from the calling
state), then your question, or rather, example, becomes:

        lua_pcall[1]
                -- code code code
                lua_pcall[2]
                        -- code code code
                        state_exit() -- returns to [1], skips [2]
                end
        -- resumed here

  So aside from the Lua state weirdness, you are trying to, for lack of a
better term, skip back to the outermost pcall?

  -spc
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Viacheslav Usov
In reply to this post by bel
On Tue, Mar 2, 2021 at 6:59 AM bel <[hidden email]> wrote:

> No, that is not what I said.

That may not be the exact words that you used, but, once given the
quotation from the manual, your claim that lua_pcall() behaved
differently was claiming a bug.

> The program should help to test out if a particular solution works or not.

Again, when discussing a (potential) bug, one should come up with a
minimal bug demo, not a whole (research) project where it may have
been (mis) observed originally.

> Another acceptable solution might be to allow calling the my_exit function only in cases where yield is allowed and return false otherwise.

Then why can't you simply wrap all of the code in a coroutine and use
yield to exit it?

Cheers,
V.
bel
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

bel
from Viacheslav Usov:

> That may not be the exact words that you used, but, once given the
> quotation from the manual, your claim that lua_pcall() behaved
> differently was claiming a bug.

I never used the word "bug" or something similar and never claimed anything like a bug in the manual or code - I don't understand why you interpret my words in that way, they were never meant like that. I only stated my test (that had a bug I did not see that time) "seems to behave differently" ... and that was meant completely neutral ... could have been that I interpret that paragraph in the manual in a different way than it was meant to be, and that's why I post some code.

> Then why can't you simply wrap all of the code in a coroutine and use
> yield to exit it?

Wouldn't this have the side effect that the Lua code can no longer use other coroutines?
The "exit" call means "this Lua state has finished the task it has been created for" and it could


from Sean Conner:

> There are a few functions that return a new Lua state:

I am using  only lua_State *lua_newstate(lua_Alloc,void *);
but I can add a lua_tothread or lua_newthread call afterwards, if this helps.
Then a lua file is loaded and executed using pcall.
I have no direct control on the content of this script file: It might use pcalls, coroutines, call some of my C functions or load other libraries with Lua or C implementation.

> So aside from the Lua state weirdness, you are trying to, for lack of a
> better term, skip back to the outermost pcall?

Yes, this is what I am trying to do.

There is an arbitrarily structured Lua code that may contain other coroutines as well, and at some point this code decides it found what it was looking for, or there is an unrecoverable problem. In a "standalone" process, you could call os.exit here, but here it should not affect the entire process but just one state.
It should not be required to surround every Lua statement in the script with a

if this_state_should_already_exit then
    statement
end

or anything like that.




On Tue, Mar 2, 2021 at 10:43 AM Viacheslav Usov <[hidden email]> wrote:
On Tue, Mar 2, 2021 at 6:59 AM bel <[hidden email]> wrote:

> No, that is not what I said.

That may not be the exact words that you used, but, once given the
quotation from the manual, your claim that lua_pcall() behaved
differently was claiming a bug.

> The program should help to test out if a particular solution works or not.

Again, when discussing a (potential) bug, one should come up with a
minimal bug demo, not a whole (research) project where it may have
been (mis) observed originally.

> Another acceptable solution might be to allow calling the my_exit function only in cases where yield is allowed and return false otherwise.

Then why can't you simply wrap all of the code in a coroutine and use
yield to exit it?

Cheers,
V.
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Viacheslav Usov
On Tue, Mar 2, 2021 at 1:16 PM bel <[hidden email]> wrote:

> I never used the word "bug" [...] and that was meant completely neutral ... could have been that I interpret that paragraph in the manual in a different way than it was meant to be, and that's why I post some code.

If you think the word 'bug' is somehow bad, fine, don't use it. It
does not matter. When you think there is a discrepancy between
documentation and observed behavior, you should report minimal repro
steps toward that discrepancy. You actually tried in your first
attempt, and it correctly demonstrated no discrepancy.

Posting your whole research project was unnecessary at that point.

> Wouldn't this have the side effect that the Lua code can no longer use other coroutines?

Possibly, but, as I said, either you do what you want by breaking
Lua's official rules, or you would have to live with some
restrictions.

This entire topic is not new. Some applications embedding Lua also
have a need to terminate a running Lua state, without bringing the
host down. This is not easy and I am not aware of a really good
solution to this. I actually think we should have support for this
built into Lua, but this won't help any current or past versions.

> There is an arbitrarily structured Lua code that may contain other coroutines as well, and at some point this code decides it found what it was looking for, or there is an unrecoverable problem.

Coroutines are not a (major) problem here. Your problem is
lua_pcall(), and we are talking about coroutines because of your
mistaken assumption that coroutines could somehow jump over
lua_pcall() boundaries.

Cheers,
V.
Reply | Threaded
Open this post in threaded view
|

Re: Exit one Lua state from a (C) API function

Oliver Kroth
In reply to this post by bel
I had a similar problem in the past: killing a running Lua process in a safe way from another OS process. 
It was solved by hooking a function into the interpreter that hooked itself again and issued and error until it finally ended the task.

    lua_State *mainthread = G(L)->mainthread;
    lua_sethook( mainthread, fullstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1 );

extern "C" void fullstop( lua_State *L, lua_Debug *ar )
{
    (void)ar;  /* unused arg. */
    lua_sethook( L, fullstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1 );
    luaL_error( L, "stopped");
}

Hope that or a variant thereof helps you.

Oliver


Am 25.02.21 um 21:58 schrieb bel:

I need to create a Lua API function to close/exit a Lua state from a Lua script.
While “os.exit” will exit the entire host application, I just want to “exit” one (of multiple) Lua states and return to the “lua_pcall” in C, while the application (and the other states) keep running.

Something like this:

my_C_function()
    lua_newstate
    lua_pcall -->
        Some Lua code
        Lua pcall -->
            more Lua code
            call "state exit" function -------------|
            Lua code that should not be executed    |
        More Lua code that should not be executed   |
   
[continue here]  <------------------------------|
    lua_close


It cannot be just a luaL_error call, since this will be caught by a pcall in the Lua code.

Is there some recommended method to do this?
It should work with Lua 5.2, 5.3 and 5.4 and use only C and Lua (no C++, no operating system dependent code or CPU dependent code like asm).

I could try to build some setjmp/longjmp code around it, but I’m wondering if there might be some built-in feature, e.g., some Lua error/exceptions that can only be handled in C but not in Lua? In my case, I will call "lua_close" immediately after and I don't need to resume any operation in this state. But for curiosity, is there a way to keep the state "fully operational" after such an operation?



12