ref: 970e4fcbaacb7511d45eaa2890501a0cc1edb45c
parent: 4833b8d20e847f59dfdb138babd7265509c0c192
author: kvik <kvik@a-b.xyz>
date: Thu Apr 22 14:53:50 EDT 2021
note: implement (horrible) note handling
--- a/mkfile
+++ b/mkfile
@@ -17,4 +17,4 @@
%.$O: %.c
$CC $CFLAGS $stem.c
-p9.$O: p9.c fs.c walk.c env.c ns.c proc.c misc.c
+p9.$O: p9.c fs.c walk.c env.c ns.c proc.c note.c misc.c
--- /dev/null
+++ b/note.c
@@ -1,0 +1,198 @@
+/*
+ * The following global state designates the Lua state
+ * responsible for registering and running note handler
+ * functions -- the one (and only) loading this module.
+ * Additionally the note itself is communicated to the
+ * postponed handler here due to most of Lua API not
+ * being safe to use in a note handler context.
+ *
+ * This global state and nondeterministic nature of
+ * postponed handling of notes means this module should
+ * be used with care and will likely need to be heavily
+ * adapted for use in any but the simplest of hosts.
+ * Lu9 standalone interpreter is an example of a simple
+ * program with a single Lua state and Lua code being
+ * "the boss", that is, the note is very likely to
+ * interrupt a Lua VM rather than host code, and if not
+ * the VM will be entered shortly after. This lets
+ * postponed note handlers run relatively close to
+ * the actual note event.
+ * In more involved programs, perhaps running multiple
+ * separate Lua states, or spending more time in the
+ * host, the postponed handlers may run only as soon
+ * as the designated handler state gets a chance to
+ * run, if at all.
+ *
+ * In short, consider alternatives to Lua code doing
+ * any direct note handling.
+ *
+ * TODO: the note state, catcher and exit functions,
+ * and notify registration should all be left for the
+ * host to set up.
+ */
+
+typedef struct Note Note;
+
+struct Note {
+ lua_State *L;
+ char note[ERRMAX+1];
+};
+
+static Note notestate;
+
+/*
+ * Acks the note so it can be handled outside note context
+ * but only after the possibly interrupted Lua state
+ * stabilizes. This is done by registering an instruction
+ * hook and running handler functions inside it.
+ * Note that another note may come just as we are doing
+ * this. We do nothing about it currently: the newer
+ * notes simply interrupt currently executing handlers.
+ * One solution is to queue incoming notes and handle
+ * all of them in order.
+ * Also note that this catcher always acknowledges the
+ * note, preventing any other catchers registered after
+ * it from ever seeing the note. Therefore you most
+ * likely want it to be the last or only note handler.
+ */
+static int
+notecatcher(void*, char *note)
+{
+ static void noterunner(lua_State*, lua_Debug*);
+
+ lua_sethook(notestate.L, noterunner,
+ LUA_MASKCALL|LUA_MASKRET|LUA_MASKCOUNT, 1);
+ strncpy(notestate.note, note, ERRMAX);
+ return 1;
+}
+
+static void
+noterunner(lua_State *L, lua_Debug*)
+{
+ int n, i;
+
+ lua_sethook(notestate.L, nil, 0, 0);
+ if(lua_getfield(L, LUA_REGISTRYINDEX, "p9-note-handlers") != LUA_TTABLE)
+ luaL_error(L, "missing note handlers table");
+ if((n = lua_rawlen(L, -1)) == 0)
+ return;
+ for(i = 1; i <= n; i++){
+ lua_rawgeti(L, -1, i); /* handler(note) */
+ lua_pushstring(L, notestate.note);
+ if(lua_pcall(L, 1, 1, 0) != LUA_OK)
+ break;
+ if(lua_toboolean(L, -1) == 1)
+ return; /* to where we got interrupted */
+ lua_pop(L, 1);
+ }
+ /* Emulate kernel handling of unacknowledged note. */
+ if(strncmp(notestate.note, "sys:", 4) == 0)
+ abort();
+ exits(notestate.note); /* threadexitsall when using thread.h */
+}
+
+static int
+noteset(lua_State *L)
+{
+ if(lua_getfield(L, LUA_REGISTRYINDEX, "p9-note-handlers") != LUA_TTABLE)
+ return luaL_error(L, "missing note handlers table");
+ lua_insert(L, -2);
+ lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
+ lua_pop(L, 1);
+ return 0;
+}
+
+static int
+noteunset(lua_State *L)
+{
+ int n, pos, fn, t;
+
+ fn = lua_gettop(L);
+ if(lua_getfield(L, LUA_REGISTRYINDEX, "p9-note-handlers") != LUA_TTABLE)
+ return luaL_error(L, "missing note handlers table");
+ t = fn + 1;
+ n = lua_rawlen(L, t);
+ for(pos = 1; pos <= n; pos++){
+ lua_rawgeti(L, t, pos);
+ if(lua_rawequal(L, fn, -1))
+ goto remove;
+ lua_pop(L, 1);
+ }
+ lua_pop(L, 2);
+ return 0;
+remove:
+ lua_pop(L, 1);
+ for ( ; pos < n; pos++) {
+ lua_rawgeti(L, t, pos + 1);
+ lua_rawseti(L, t, pos);
+ }
+ lua_pushnil(L);
+ lua_rawseti(L, t, pos);
+ lua_pop(L, 2);
+ return 0;
+}
+
+static int
+p9_note_catch(lua_State *L)
+{
+ int fn;
+ const char *arg;
+
+ fn = lua_gettop(L);
+ luaL_argexpected(L, fn > 0, 1, "function expected");
+ if(fn == 1)
+ arg = "set";
+ else
+ arg = luaL_checkstring(L, 1);
+ luaL_argexpected(L,
+ lua_type(L, fn) == LUA_TFUNCTION, fn, "function");
+ if(strcmp(arg, "set") == 0)
+ return noteset(L);
+ else if(strcmp(arg, "unset") == 0)
+ return noteunset(L);
+ return luaL_error(L, "'set' or 'unset' expected");
+}
+
+static int
+p9_note_post(lua_State *L)
+{
+ int pid, w;
+ const char *who, *note;
+
+ who = luaL_checkstring(L, 1);
+ if(strcmp(who, "proc") == 0)
+ w = PNPROC;
+ else if(strcmp(who, "group") == 0)
+ w = PNGROUP;
+ else
+ return luaL_argerror(L, 1, "expected 'proc' or 'group'");
+ pid = luaL_checkinteger(L, 2);
+ note = luaL_checkstring(L, 3);
+ if(postnote(w, pid, note) == -1)
+ return error(L, "postnote: %r");
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static luaL_Reg p9_note_funcs[] = {
+ {"post", p9_note_post},
+ {"catch", p9_note_catch},
+ {nil, nil},
+};
+
+int
+luaopen_p9_note(lua_State *L)
+{
+ /* Only one Lua state may work with notes */
+ if(notestate.L != nil)
+ return 0;
+ notestate.L = L;
+
+ lua_createtable(L, 1, 0);
+ lua_setfield(L, LUA_REGISTRYINDEX, "p9-note-handlers");
+
+ luaL_newlib(L, p9_note_funcs);
+
+ atnotify(notecatcher, 1);
+ return 1;
+}
--- a/p9.c
+++ b/p9.c
@@ -96,6 +96,7 @@
#include "ns.c"
#include "proc.c"
#include "misc.c"
+#include "note.c"
typedef struct Data {
char *key;