Frage Lua Koroutinen - Setjmp Longjmp Clobbering?


In einem Blogeintrag Vor nicht allzu langer Zeit beschreibt Scott Vokes ein technisches Problem, das mit Luas Implementierung von Koroutinen unter Verwendung der C-Funktionen verbunden ist setjmp und longjmp:

Die Haupteinschränkung von Lua-Koroutinen besteht darin, dass sie, da sie mit setjmp (3) und longjmp (3) implementiert sind, sie nicht dazu verwenden können, von Lua in C-Code zu rufen, der zurück in Lua ruft, das in C zurück aufruft, weil der verschachtelte longjmp wird die Stack-Frames der C-Funktion überlisten. (Dies wird zur Laufzeit erkannt und nicht im Hintergrund ausgeführt.)

Ich habe nicht festgestellt, dass dies ein Problem in der Praxis ist, und ich bin mir keiner Möglichkeit bewusst, es zu reparieren, ohne Lua's Portabilität zu beschädigen, eines meiner Lieblingssachen über Lua - es wird buchstäblich alles mit einem ANSI C Compiler laufen lassen eine bescheidene Menge an Platz. Mit Lua kann ich leicht reisen. :)

Ich habe Koroutinen eine angemessene Menge verwendet, und ich dachte, dass ich im Allgemeinen verstand, was los war und was setjmp und longjmp Aber ich habe das irgendwann gelesen und festgestellt, dass ich es nicht wirklich verstanden habe. Um zu versuchen, es herauszufinden, habe ich versucht, ein Programm zu machen, von dem ich dachte, dass es aufgrund der Beschreibung ein Problem verursachen sollte, und stattdessen scheint es gut zu funktionieren.

Allerdings gibt es ein paar andere Orte, an denen ich Leute gesehen zu haben scheinen, dass es Probleme gibt:

Die Frage ist:

  • Unter welchen Umständen funktionieren Lua-Coroutinen nicht, weil C-Funktion-Stack-Frames verfälscht werden?
  • Was genau ist das Ergebnis? Bedeutet "zur Laufzeit erkannt", lua panic? Oder etwas anderes?
  • Betrifft das immer noch die neuesten Versionen von lua (5.3) oder ist das tatsächlich ein 5.1-Problem oder etwas?

Hier war der Code, den ich produziert habe. In meinem Test ist es mit lua 5.3.1 verknüpft, kompiliert als C-Code, und der Test selbst wird als C ++ - Code nach C ++ 11-Standard kompiliert.

extern "C" {
#include <lauxlib.h>
#include <lua.h>
}

#include <cassert>
#include <iostream>

#define CODE(C) \
case C: { \
  std::cout << "When returning to " << where << " got code '" #C "'" << std::endl; \
  break; \
}

void handle_resume_code(int code, const char * where) {
  switch (code) {
    CODE(LUA_OK)
    CODE(LUA_YIELD)
    CODE(LUA_ERRRUN)
    CODE(LUA_ERRMEM)
    CODE(LUA_ERRERR)
    default:
      std::cout << "An unknown error code in " << where << std::endl;
  }
}

int trivial(lua_State *, int, lua_KContext) {
  std::cout << "Called continuation function" << std::endl;
  return 0;
}

int f(lua_State * L) {
  std::cout << "Called function 'f'" << std::endl;
  return 0;
}

int g(lua_State * L) {
  std::cout << "Called function 'g'" << std::endl;

  lua_State * T = lua_newthread(L);
  lua_getglobal(T, "f");

  handle_resume_code(lua_resume(T, L, 0), __func__);
  return lua_yieldk(L, 0, 0, trivial);
}

int h(lua_State * L) {
  std::cout << "Called function 'h'" << std::endl;

  lua_State * T = lua_newthread(L);
  lua_getglobal(T, "g");

  handle_resume_code(lua_resume(T, L, 0), __func__);
  return lua_yieldk(L, 0, 0, trivial);
}

int main () {
  std::cout << "Starting:" << std::endl;

  lua_State * L = luaL_newstate();

  // init
  {
    lua_pushcfunction(L, f);
    lua_setglobal(L, "f");

    lua_pushcfunction(L, g);
    lua_setglobal(L, "g");

    lua_pushcfunction(L, h);
    lua_setglobal(L, "h");
  }

  assert(lua_gettop(L) == 0);

  // Some action
  {
    lua_State * T = lua_newthread(L);
    lua_getglobal(T, "h");

    handle_resume_code(lua_resume(T, nullptr, 0), __func__);
  }

  lua_close(L); 

  std::cout << "Bye! :-)" << std::endl;
}

Die Ausgabe, die ich bekomme, ist:

Starting:
Called function 'h'
Called function 'g'
Called function 'f'
When returning to g got code 'LUA_OK'
When returning to h got code 'LUA_YIELD'
When returning to main got code 'LUA_YIELD'
Bye! :-)

Vielen Dank an @ Nicol Bolas für die sehr detaillierte Antwort!
Nachdem ich seine Antwort gelesen, die offiziellen Dokumente gelesen, einige E-Mails gelesen und etwas mehr damit herumgespielt habe, möchte ich die Frage präzisieren / eine bestimmte Folgefrage stellen, wie auch immer man sie ansehen möchte.

Ich denke, dieser Begriff "Clobbering" ist nicht gut für die Beschreibung dieses Problems und das war Teil dessen, was mich verwirrte - nichts wird "durchgeschüttelt" in dem Sinne, zweimal geschrieben zu werden und der erste Wert ist verloren, das Problem ist allein, wie @ Nicol Bolas darauf hinweist, dass longjmp wirft einen Teil des C-Stacks und wenn Sie hoffen, den Stack später wiederherzustellen, schade.

Das Thema wird eigentlich sehr schön beschrieben Abschnitt 4.7 von Lua 5.2 Handbuch, in einem Link von @ Nicol Bolas zur Verfügung gestellt.

Seltsamerweise gibt es in der lua 5.1-Dokumentation keinen entsprechenden Abschnitt. Allerdings hat Lua 5.2 das zu sagen Über lua_yieldk:

Liefert eine Coroutine.

Diese Funktion sollte nur als Rückgabeausdruck einer C-Funktion wie folgt aufgerufen werden:

return lua_yieldk (L, n, i, k);

Lua 5.1 Handbuch sagt sowas ähnliches, Über lua_yield stattdessen:

Liefert eine Coroutine.

Diese Funktion sollte nur als Rückgabeausdruck einer C-Funktion wie folgt aufgerufen werden:

return lua_yieldk (L, n, i, k);

Einige natürliche Fragen dann:

  • Warum ist es wichtig, wenn ich benutze? return hier oder nicht? Ob lua_yieldk werde anrufen longjmp dann ist die lua_yieldk wird sowieso nie zurückkehren, also sollte es egal sein, wenn ich dann zurückkomme? Das kann also nicht sein, was passiert, oder?
  • Nehmen wir stattdessen das an lua_yieldk macht nur eine Notiz im Lua-Staat, dass der aktuelle CAPI-Aufruf erklärt hat, dass er nachgeben will, und wenn es dann endlich zurückkehrt, wird Lua herausfinden, was als nächstes passiert. Dann löst dies das Problem des Speicherns von C-Stapelrahmen, nein? Da, nachdem wir normal nach Lua zurückgekehrt sind, diese Stack Frames sowieso schon abgelaufen sind - also werden die im @ Nicol Bolas Bild beschriebenen Komplikationen umgangen? Und zweitens, in 5.2 zumindest ist die Semantik nie so, dass wir C-Stack-Frames wiederherstellen sollten, so scheint es - lua_yieldk Fortsetzung zu einer Fortsetzungsfunktion, nicht zu der lua_yieldk Anrufer und lua_yield scheinbar zum Anrufer des aktuellen api Anrufs, nicht zu dem lua_yield Anrufer selbst.

Und die wichtigste Frage:

Wenn ich konsequent benutze lua_yieldk in der Form return lua_yieldk(...) in den Dokumenten angegeben und von a zurückgegeben lua_CFunction das wurde an lua weitergegeben, ist es noch möglich das zu lösen attempt to yield across a C-call boundary Error?

Schließlich, (aber das ist weniger wichtig), würde ich gerne ein konkretes Beispiel sehen, wie es aussieht, wenn ein naive Programmierer "nicht vorsichtig ist" und den attempt to yield across a C-call boundary Error. Ich habe die Idee, dass damit ein Problem verbunden sein könnte setjmp und longjmp wir werfen Stack-Frames, die wir später brauchen, aber ich möchte einen echten lua / lua-capi-Code sehen, auf den ich zeigen kann und zum Beispiel "tue das nicht", und das ist überraschend schwer fassbar.

ich fand diese E-Mail wo jemand diesen Fehler mit einigen lua 5.1 Code gemeldet hat, und ich habe versucht, es in lua 5.3 zu reproduzieren. Was ich jedoch fand, war, dass dies nur ein Fehlerbericht aus der lua-Implementierung ist - der eigentliche Fehler wird verursacht, weil der Benutzer seine Coroutine nicht richtig eingerichtet hat. Die richtige Methode zum Laden der Coroutine ist, erstellen Sie den Thread, drücken Sie eine Funktion auf den Thread-Stack, und rufen Sie dann lua_resume auf dem Thread-Status. Stattdessen verwendete der Benutzer dofile auf dem Thread-Stack, der die Funktion dort nach dem Laden ausführt, statt sie wieder aufzunehmen. So ist es effektiv yield outside of a coroutine iiuc, und wenn ich dies patch, funktioniert sein Code gut, beide zu verwenden lua_yield und lua_yieldk in lua 5.3.

Hier ist die Liste, die ich erstellt habe:

#include <cassert>
#include <cstdio>

extern "C" {
#include "lua.h"
#include "lauxlib.h"
}

//#define USE_YIELDK

bool running = true;

int lua_print(lua_State * L) {
  if (lua_gettop(L)) {
    printf("lua: %s\n", lua_tostring(L, -1));
  }
  return 0;
}

int lua_finish(lua_State *L) {
  running = false;
  printf("%s called\n", __func__);
  return 0;
}

int trivial(lua_State *, int, lua_KContext) {
  printf("%s called\n", __func__);
  return 0;
}

int lua_sleep(lua_State *L) {
  printf("%s called\n", __func__);
#ifdef USE_YIELDK
  printf("Calling lua_yieldk\n");
  return lua_yieldk(L, 0, 0, trivial);
#else
  printf("Calling lua_yield\n");
  return lua_yield(L, 0);
#endif
}

const char * loop_lua =
"print(\"loop.lua\")\n"
"\n"
"local i = 0\n"
"while true do\n"
"  print(\"lua_loop iteration\")\n"
"  sleep()\n"
"\n"
"  i = i + 1\n"
"  if i == 4 then\n"
"    break\n"
"  end\n"
"end\n"
"\n"
"finish()\n";

int main() {
  lua_State * L = luaL_newstate();

  lua_pushcfunction(L, lua_print);
  lua_setglobal(L, "print");

  lua_pushcfunction(L, lua_sleep);
  lua_setglobal(L, "sleep");

  lua_pushcfunction(L, lua_finish);
  lua_setglobal(L, "finish");

  lua_State* cL = lua_newthread(L);
  assert(LUA_OK == luaL_loadstring(cL, loop_lua));
  /*{
    int result = lua_pcall(cL, 0, 0, 0);
    if (result != LUA_OK) {
      printf("%s error: %s\n", result == LUA_ERRRUN ? "Runtime" : "Unknown", lua_tostring(cL, -1));
      return 1;
    }
  }*/
  // ^ This pcall (predictably) causes an error -- if we try to execute the
  // script, it is going to call things that attempt to yield, but we did not
  // start the script with lua_resume, we started it with pcall, so it's not
  // okay to yield.
  // The reported error is "attempt to yield across a C-call boundary", but what
  // is really happening is just "yield from outside a coroutine" I suppose...

  while (running) {
    int status;
    printf("Waking up coroutine\n");
    status = lua_resume(cL, L, 0);
    if (status == LUA_YIELD) {
      printf("coroutine yielding\n");
    } else {
      running = false; // you can't try to resume if it didn't yield

      if (status == LUA_ERRRUN) {
        printf("Runtime error: %s\n", lua_isstring(cL, -1) ? lua_tostring(cL, -1) : "(unknown)" );
        lua_pop(cL, -1);
        break;
      } else if (status == LUA_OK) {
        printf("coroutine finished\n");
      } else {
        printf("Unknown error\n");
      }
    }
  }

  lua_close(L);
  printf("Bye! :-)\n");
  return 0;
}

Hier ist die Ausgabe wann USE_YIELDK ist auskommentiert:

Waking up coroutine
lua: loop.lua
lua: lua_loop iteration
lua_sleep called
Calling lua_yield
coroutine yielding
Waking up coroutine
lua: lua_loop iteration
lua_sleep called
Calling lua_yield
coroutine yielding
Waking up coroutine
lua: lua_loop iteration
lua_sleep called
Calling lua_yield
coroutine yielding
Waking up coroutine
lua: lua_loop iteration
lua_sleep called
Calling lua_yield
coroutine yielding
Waking up coroutine
lua_finish called
coroutine finished
Bye! :-)

Hier ist die Ausgabe wann USE_YIELDK ist definiert:

Waking up coroutine
lua: loop.lua
lua: lua_loop iteration
lua_sleep called
Calling lua_yieldk
coroutine yielding
Waking up coroutine
trivial called
lua: lua_loop iteration
lua_sleep called
Calling lua_yieldk
coroutine yielding
Waking up coroutine
trivial called
lua: lua_loop iteration
lua_sleep called
Calling lua_yieldk
coroutine yielding
Waking up coroutine
trivial called
lua: lua_loop iteration
lua_sleep called
Calling lua_yieldk
coroutine yielding
Waking up coroutine
trivial called
lua_finish called
coroutine finished
Bye! :-)

8
2017-12-16 03:33


Ursprung


Antworten:


Denken Sie darüber nach, was passiert, wenn eine Coroutine eine yield. Es stoppt die Ausführung und die Verarbeitung kehrt zu dem zurück, der aufgerufen wurde resume auf dieser Koroutine, richtig?

Nun, sagen wir, Sie haben diesen Code:

function top()
    coroutine.yield()
end

function middle()
    top()
end

function bottom()
    middle()
end

local co = coroutine.create(bottom);

coroutine.resume(co);

Im Moment des Anrufs nach yieldDer Lua-Stack sieht folgendermaßen aus:

-- top
-- middle
-- bottom
-- yield point

Wenn du anrufst yieldwird der Lua-Aufrufstapel, der Teil der Coroutine ist, beibehalten. Wenn Sie das tun resume, der erhaltene Call-Stack wird erneut ausgeführt, beginnend dort, wo er vorher aufgehört hat.

OK, sagen wir das jetzt middle war in der Tat keine Lua-Funktion. Stattdessen war es eine C-Funktion, und diese C-Funktion ruft die Lua-Funktion auf top. Ihr Konzept sieht also so aus:

-- Lua - top
-- C   - middle
-- Lua - bottom
-- Lua - yield point

Bitte beachten Sie, was ich vorher gesagt habe: So sieht Ihr Stack aus konzeptionell.

Weil Ihr tatsächlicher Call-Stack aussieht nichts dergleichen.

In Wirklichkeit gibt es wirklich zwei Stapel. Es gibt Lua's internen Stack, definiert durch ein lua_State. Und da ist Cs Stack. Luas interner Stapel, zu der Zeit als yield wird ungefähr genannt, sieht ungefähr so ​​aus:

-- top
-- Some C stuff
-- bottom
-- yield point

Also, wie sieht der Stack zu C aus? Nun, es sieht so aus:

-- arbitrary Lua interpreter stuff
-- middle
-- arbitrary Lua interpreter stuff
-- setjmp

Und genau da ist das Problem. Sehen Sie, wenn Lua ein tut yieldEs wird anrufen longjmp. Diese Funktion basiert auf dem Verhalten des C-Stacks. Nämlich, es wird dorthin zurückkehren, wo setjmp war.

Der Lua-Stapel wird beibehalten, weil der Lua-Stapel ist trennen vom C-Stapel. Aber der C-Stapel? Alles zwischen den longjmp und setjmp?. Weg. Kaputt. Hat verloren für immer.

Jetzt kannst du gehen, "warte, weiß der Lua-Stapel nicht, dass es in C und zurück in Lua ging"? Ein bisschen. Aber der Lua-Stapel ist unfähig, etwas zu tun, zu dem C nicht in der Lage ist. Und C ist einfach nicht in der Lage, einen Stapel zu konservieren (gut, nicht ohne spezielle Bibliotheken). Während also der Lua-Stack sich vage bewusst ist, dass ein C-Prozess in der Mitte seines Stacks stattgefunden hat, kann er nicht rekonstruieren, was da war.

Was passiert also, wenn Sie das fortsetzen? yieldEd Coroutine?

Nasale Dämonen. Und niemand mag diese. Zum Glück, Lua 5.1 und höher (zumindest) wird Fehler, wenn Sie versuchen, über C. zu liefern

Beachten Sie, dass Lua 5.2+ hat Möglichkeiten, dies zu beheben. Aber es ist nicht automatisch; es erfordert eine explizite Codierung Ihrerseits.

Wenn Lua-Code, der in einer Coroutine ist, Ihren C-Code aufruft und Ihr C-Code Lua-Code aufruft, den Sie erhalten, können Sie verwenden lua_callk oder lua_pcallk die möglicherweise ergebenden Lua-Funktionen aufrufen. Diese aufrufenden Funktionen nehmen einen zusätzlichen Parameter: eine "Fortsetzungs" -Funktion.

Wenn der Lua - Code, den Sie anrufen, nachgibt, dann wird der lua_*callk Funktion wird niemals wirklich zurückkehren (da Ihr C-Stack zerstört wurde). Stattdessen ruft es die Fortsetzungsfunktion auf, die Sie in Ihrem Konto angegeben haben lua_*callk Funktion. Wie Sie anhand des Namens erraten können, besteht der Job der Fortsetzungsfunktion darin, dort fortzufahren, wo Ihre vorherige Funktion nicht mehr aktiv war.

Nun behält Lua den Stapel für die Fortsetzungsfunktion bei, so dass der Stapel in dem Zustand ist, in dem sich die ursprüngliche C-Funktion befand. Nun, außer dass die Funktion + Argumente, die Sie aufgerufen haben (mit lua_*callk) werden entfernt, und die Rückgabewerte dieser Funktion werden auf Ihren Stapel geschoben. Abgesehen davon ist der Stapel alle gleich.

Es gibt auch lua_yieldk. Dadurch kann Ihre C-Funktion zu Lua zurückkehren, so dass bei Wiederaufnahme der Coroutine die vorgesehene Fortsetzungsfunktion aufgerufen wird.

Beachten Sie, dass Coco gibt Lua 5.1 die Möglichkeit, dieses Problem zu lösen. Es ist in der Lage (obwohl OS / Assembly / etc Magie) von bewahren der C-Stapel während einer Yield-Operation. LuaJIT-Versionen vor 2.0 bieten diese Funktion ebenfalls.


C ++ Hinweis

Sie haben Ihre Frage mit dem C ++ - Tag markiert, also nehme ich an, dass das hier eine Rolle spielt.

Zu den vielen Unterschieden zwischen C und C ++ gehört die Tatsache, dass C ++ ist weit mehr abhängig von der Art seiner Callstack als Lua. Wenn Sie in C einen Stapel verwerfen, können Ressourcen verloren gehen, die nicht bereinigt wurden. C ++ ist jedoch erforderlich, um Destruktoren von Funktionen aufzurufen, die auf dem Stapel an einem bestimmten Punkt deklariert sind. Der Standard erlaubt es nicht, sie einfach wegzuwerfen.

Fortsetzungen funktionieren also nur in C ++, wenn es solche gibt nichts auf dem Stapel, der einen Destruktoraufruf haben muss. Oder genauer gesagt, nur Typen, die trivial zerstörbar sind, können auf dem Stapel sitzen, wenn Sie eine der Fortsetzungsfunktions-Lua-APIs aufrufen.

Natürlich behandelt Coco C ++ sehr gut, da es den C ++ - Stack enthält.


9
2017-12-16 05:00



Posting dies als eine Antwort, die @ Nicol Bolas Antwort ergänzt, und so Ich kann Platz haben, um aufzuschreiben, was ich gebraucht habe, um das Original zu verstehen Frage und die Antworten auf die sekundären Fragen / eine Code-Liste.

Wenn Sie die Antwort von Nicol Bolas lesen, aber noch Fragen wie ich haben, hier sind einige zusätzliche Hinweise:

  • Das drei Ebenen auf dem Aufruf-Stack, Lua, C, Lua, sind wesentlich für das Problem. Wenn Sie nur zwei Schichten haben, Lua und C, haben Sie das Problem nicht.
  • Wenn man sich vorstellt, wie der Coroutine-Aufruf funktionieren soll, sieht der Lua-Stack aus In gewisser Weise sieht der C-Stack in gewisser Weise so aus, dass der Call (longjmp) und später wird wieder aufgenommen ... das Problem passiert nicht sofort wann ist es wieder aufgenommen.
    Das Problem tritt auf, wenn die wiederaufgenommene Funktion später versucht, zu Ihrem zurückzukehren C-Funktion.
    Weil die Koroutinensemantik funktionieren soll, soll sie zurückkehren in einen C-Funktionsaufruf, aber die Stapelrahmen dafür sind weg und können nicht sein restauriert.
  • Die Problemumgehung für Dies Mangel an Fähigkeit, diese Stapelrahmen wiederherzustellen, ist zu benutzen lua_callk, lua_pcallk, die es Ihnen ermöglichen, einen Ersatz bereitzustellen Funktion, die anstelle der C-Funktion aufgerufen werden kann, deren Frames waren ausgelöscht.
  • Das Problem über return lua_yieldk(...) scheint nichts zu tun zu haben irgendeins von diesen. Vom Skimming der Implementierung von lua_yieldk anscheinend Es ist in der Tat immer longjmpund es kann nur in einem obskuren Fall zurückkehren mit Lua Debugging-Haken (?).
  • Lua intern (in der aktuellen Version) verfolgt, wenn Ertrag sollte nicht sein erlaubt, indem eine Zählervariable gehalten wird nny (Nummer nicht nachgiebig) zugeordnet in den Lua-Staat, und wenn Sie anrufen lua_call oder lua_pcall von einem CAPI Funktion (a lua_CFunction was du früher nach lua geschoben hast), nny ist inkrementiert und wird nur dekrementiert, wenn dieser Aufruf oder pcall zurückkehrt. Wann nnyist nicht null, es ist nicht sicher, nachzugeben, und Sie bekommen das yield across C-api boundary Fehler, wenn Sie versuchen, trotzdem nachzugeben.

Hier ist eine einfache Auflistung, die das Problem erzeugt und die Fehler meldet, wenn du wie ich bist und gerne konkrete Code-Beispiele hast. Es demonstriert einige der Unterschiede bei der Verwendung lua_call, lua_pcall, und lua_pcallk innerhalb einer Funktion, die von einer Coroutine aufgerufen wird.

extern "C" {
#include <lauxlib.h>
#include <lua.h>
}

#include <cassert>
#include <iostream>

//#define USE_PCALL
//#define USE_PCALLK

#define CODE(C) \
case C: { \
  std::cout << "When returning to " << where << " got code '" #C "'" << std::endl; \
  break; \
}

#define ERRCODE(C) \
case C: { \
  std::cout << "When returning to " << where << " got code '" #C "': " << lua_tostring(L, -1) << std::endl; \
  break; \
}

int report_resume_code(int code, const char * where, lua_State * L) {
  switch (code) {
    CODE(LUA_OK)
    CODE(LUA_YIELD)
    ERRCODE(LUA_ERRRUN)
    ERRCODE(LUA_ERRMEM)
    ERRCODE(LUA_ERRERR)
    default:
      std::cout << "An unknown error code in " << where << ": " << lua_tostring(L, -1) << std::endl;
  }
  return code;
}

int report_pcall_code(int code, const char * where, lua_State * L) {
  switch(code) {
    CODE(LUA_OK)
    ERRCODE(LUA_ERRRUN)
    ERRCODE(LUA_ERRMEM)
    ERRCODE(LUA_ERRERR)
    default:
      std::cout << "An unknown error code in " << where << ": " << lua_tostring(L, -1) << std::endl;
  }
  return code;
}

int trivial(lua_State *, int, lua_KContext) {
  std::cout << "Called continuation function" << std::endl;
  return 0;
}

int f(lua_State * L) {
  std::cout << "Called function 'f', yielding" << std::endl;
  return lua_yield(L, 0);
}

int g(lua_State * L) {
  std::cout << "Called function 'g'" << std::endl;

  lua_getglobal(L, "f");
#ifdef USE_PCALL
  std::cout  << "pcall..." << std::endl;
  report_pcall_code(lua_pcall(L, 0, 0, 0), __func__, L);
  // ^ yield across pcall!
  // If we yield, there is no way ever to return normally from this pcall,
  // so it is an error.
#elif defined(USE_PCALLK)
  std::cout  << "pcallk..." << std::endl;
  report_pcall_code(lua_pcallk(L, 0, 0, 0, 0, trivial), __func__, L);
#else
  std::cout << "call..." << std::endl;
  lua_call(L, 0, 0);
  // ^ yield across call!
  // This results in an error being reported in lua_resume, rather than at
  // the pcall
#endif
  return 0;
}

int main () {
  std::cout << "Starting:" << std::endl;

  lua_State * L = luaL_newstate();

  // init
  {
    lua_pushcfunction(L, f);
    lua_setglobal(L, "f");

    lua_pushcfunction(L, g);
    lua_setglobal(L, "g");
  }

  assert(lua_gettop(L) == 0);

  // Some action
  {
    lua_State * T = lua_newthread(L);
    lua_getglobal(T, "g");

    while (LUA_YIELD == report_resume_code(lua_resume(T, L, 0), __func__, T)) {}
  }

  lua_close(L); 

  std::cout << "Bye! :-)" << std::endl;
}

Beispielausgabe:

call

Starting:
Called function 'g'
call...
Called function 'f', yielding
When returning to main got code 'LUA_ERRRUN': attempt to yield across a C-call boundary
Bye! :-)

pcall

Starting:
Called function 'g'
pcall...
Called function 'f', yielding
When returning to g got code 'LUA_ERRRUN': attempt to yield across a C-call boundary
When returning to main got code 'LUA_OK'
Bye! :-)

pcallk

Starting:
Called function 'g'
pcallk...
Called function 'f', yielding
When returning to main got code 'LUA_YIELD'
Called continuation function
When returning to main got code 'LUA_OK'
Bye! :-)

1
2017-12-18 07:16