From 06d2ef67e5ae9d90113c320096d6963a60ddc664 Mon Sep 17 00:00:00 2001 From: Konstantin Osipov <kostja.osipov@gmail.com> Date: Fri, 14 Oct 2011 18:03:55 +0400 Subject: [PATCH] Lua: initial support for box.fiber library Add fiber.cancel(), fiber.sleep(), fiber.testcancel(), fiber.id(). Draft the fiber library interface description. --- core/admin.m | 2 + core/admin.rl | 2 + core/fiber.m | 5 ++ core/tarantool_lua.m | 164 ++++++++++++++++++++++++++++++++++++++++++- include/tarantool.h | 5 ++ mod/box/box_lua.m | 23 +----- test/box/lua.result | 37 +++++++++- test/box/lua.test | 10 +++ 8 files changed, 223 insertions(+), 25 deletions(-) diff --git a/core/admin.m b/core/admin.m index c17bda4919..8a09846fee 100644 --- a/core/admin.m +++ b/core/admin.m @@ -1469,6 +1469,8 @@ admin_handler(void *data __attribute__((unused))) { lua_State *L = lua_newthread(tarantool_L); int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX); + /** Allow to interrupt/kill administrative connections. */ + fiber_setcancelstate(true); @try { for (;;) { if (admin_dispatch(L) <= 0) diff --git a/core/admin.rl b/core/admin.rl index 9c315f1686..6a156cb80d 100644 --- a/core/admin.rl +++ b/core/admin.rl @@ -218,6 +218,8 @@ admin_handler(void *data __attribute__((unused))) { lua_State *L = lua_newthread(tarantool_L); int coro_ref = luaL_ref(tarantool_L, LUA_REGISTRYINDEX); + /** Allow to interrupt/kill administrative connections. */ + fiber_setcancelstate(true); @try { for (;;) { if (admin_dispatch(L) <= 0) diff --git a/core/fiber.m b/core/fiber.m index 6c32f63272..04ca385f0d 100644 --- a/core/fiber.m +++ b/core/fiber.m @@ -158,6 +158,11 @@ fiber_cancel(struct fiber *f) f->flags |= FIBER_CANCEL; + if (f == fiber) { + fiber_testcancel(); + return; + } + if (f->flags & FIBER_CANCELLABLE) fiber_wakeup(f); diff --git a/core/tarantool_lua.m b/core/tarantool_lua.m index ac998e3807..c5dde48b60 100644 --- a/core/tarantool_lua.m +++ b/core/tarantool_lua.m @@ -40,6 +40,11 @@ struct lua_State *tarantool_L; +/** {{{ box Lua library: common functions + */ + +const char *boxlib_name = "box"; + /** Pack our BER integer into luaL_Buffer */ static void luaL_addvarint32(luaL_Buffer *b, u32 u32) @@ -188,7 +193,8 @@ lbox_unpack(struct lua_State *L) } return i-2; } -/** A descriptor for box.tbuf object methods */ + +/** A descriptor for box methods */ static const struct luaL_reg boxlib[] = { {"pack", lbox_pack}, @@ -196,6 +202,134 @@ static const struct luaL_reg boxlib[] = { {NULL, NULL} }; +/* }}} */ + +/** {{{ box.fiber Lua library: access to Tarantool/Box fibers + * + * Each fiber can be running, suspended or dead. + * It can also be attached or detached. + * All fibers are part of the fiber registry, + * box.fiber. This registry can be searched either by + * fiber id (fid), which is numeric, or by fiber name, + * which is a string. If there is more than one + * fiber with the given name, the first fiber that + * matches is returned. + * + * A new fiber can also be created from Lua, with + * fiber.create(). The fiber + * is created suspended, and is thus "attached" + * to the creator. The creator can invoke the suspended + * fiber with fiber.resume(), and the invoked fiber + * can yield control back to the caller with fiber.yield(). + * + * An active fiber, however, may choose to "detach" + * from the caller, and become part of the main Tarantool/Box + * loop. This is what all internal Tarantool/Box fibers + * are -- detached. To "detach" from its creator, + * a running fiber can use fiber.detach(). A detached + * fiber becomes attached to the internal "sched" + * fiber, and can not be attached back to its original + * creator. + * + * Once fiber chunk is done or calls "return", + * the fiber is considered dead. Its carcass is put into + * fiber pool, and can be reused when another fiber is + * created. + * + * A runaway fiber can be stopped with fiber.cancel(). + * fiber.cancel(), however, is advisory -- it works + * only if the runaway fiber is calling fiber.testcancel() + * once in a while. Most box.* hooks, such as box.delete() + * or box.update(), are calling fiber.testcancel(). + * Thus a runaway fiber can really only become cuckoo + * if it does a lot of computations and doesn't check + * whether it's been cancelled (just don't do that). + */ + +static const char *fiberlib_name = "box.fiber"; + +struct fiber * +lbox_checkfiber(struct lua_State *L, int index) +{ + return *(void **) luaL_checkudata(L, index, fiberlib_name); +} + +int +lbox_fiber_id(struct lua_State *L) +{ + struct fiber *f = lbox_checkfiber(L, 1); + lua_pushinteger(L, f->fid); + return 1; +} + +/** Yield to the sched fiber and sleep. + * @param[in] amount of time to sleep (double) + * + * Only the current fiber can be made to sleep. + */ +int +lbox_fiber_sleep(struct lua_State *L) +{ + if (! lua_isnumber(L, 1) || lua_gettop(L) != 1) + luaL_error(L, "fiber.sleep(delay): bad arguments"); + double delay = lua_tonumber(L, 1); + fiber_sleep(delay); + return 0; +} + +int +lbox_fiber_self(struct lua_State *L) +{ + void **ptr = lua_newuserdata(L, sizeof(void *)); + luaL_getmetatable(L, fiberlib_name); + lua_setmetatable(L, -2); + *ptr = fiber; + return 1; +} + +/** Running and suspended fibers can be cancelled. + * Zombie fibers can't. + */ +int +lbox_fiber_cancel(struct lua_State *L) +{ + struct fiber *f = lbox_checkfiber(L, 1); + if (! (f->flags & FIBER_CANCELLABLE)) + luaL_error(L, "fiber.cancel(): subject fiber does " + "not permit cancel"); + fiber_cancel(f); + return 0; +} + +/** + * Check if this current fiber has been cancelled and + * throw an exception if this is the case. + */ + +int +lbox_fiber_testcancel(struct lua_State *L) +{ + if (lua_gettop(L) != 0) + luaL_error(L, "fiber.testcancel(): bad arguments"); + fiber_testcancel(); + return 0; +} + +static const struct luaL_reg lbox_fiber_meta [] = { + {"id", lbox_fiber_id}, + {NULL, NULL} +}; + +static const struct luaL_reg fiberlib[] = { + {"sleep", lbox_fiber_sleep}, + {"self", lbox_fiber_self}, + {"cancel", lbox_fiber_cancel}, + {"testcancel", lbox_fiber_testcancel}, + {NULL, NULL} +}; + +/* }}} */ + /* * lua_tostring does no use __tostring metamethod, and it has * to be called if we want to print Lua userdata correctly. @@ -273,6 +407,25 @@ lbox_print(struct lua_State *L) return 0; } +/** A helper to register a single type metatable. */ +void +tarantool_lua_register_type(struct lua_State *L, const char *type_name, + const struct luaL_Reg *methods) +{ + luaL_newmetatable(L, type_name); + /* + * Conventionally, make the metatable point to itself + * in __index. If 'methods' contain a field for __index, + * this is a no-op. + */ + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + lua_pushstring(L, type_name); + lua_setfield(L, -2, "__metatable"); + luaL_register(L, NULL, methods); + lua_pop(L, 1); +} + struct lua_State * tarantool_lua_init() { @@ -280,8 +433,11 @@ tarantool_lua_init() if (L == NULL) return L; luaL_openlibs(L); - luaL_register(L, "box", boxlib); + luaL_register(L, boxlib_name, boxlib); + lua_pop(L, 1); + luaL_register(L, fiberlib_name, fiberlib); lua_pop(L, 1); + tarantool_lua_register_type(L, fiberlib_name, lbox_fiber_meta); lua_register(L, "print", lbox_print); L = mod_lua_init(L); lua_settop(L, 0); /* clear possible left-overs of init */ @@ -415,3 +571,7 @@ void tarantool_lua_load_cfg(struct lua_State *L, struct tarantool_cfg *cfg) lua_pcall(L, 0, 0, 0); lua_pop(L, 1); } + +/* + * vim: foldmethod=marker + */ diff --git a/include/tarantool.h b/include/tarantool.h index c92abfe7d0..b9c7abea3d 100644 --- a/include/tarantool.h +++ b/include/tarantool.h @@ -31,6 +31,7 @@ #include <log_io.h> struct lua_State; +struct luaL_Reg; extern struct recovery_state *recovery_state; void mod_init(void); @@ -51,6 +52,10 @@ void mod_info(struct tbuf *out); */ struct lua_State *mod_lua_init(struct lua_State *L); +void +tarantool_lua_register_type(struct lua_State *L, const char *type_name, + const struct luaL_Reg *methods); + /** * Create an instance of Lua interpreter and load it with * Tarantool modules. Creates a Lua state, imports global diff --git a/mod/box/box_lua.m b/mod/box/box_lua.m index 86cbb86613..3819526b72 100644 --- a/mod/box/box_lua.m +++ b/mod/box/box_lua.m @@ -631,35 +631,16 @@ void box_lua_call(struct box_txn *txn __attribute__((unused)), } } -/** A helper to register a single type metatable. */ -static void -lua_register_type(struct lua_State *L, const char *type_name, - const struct luaL_reg *methods) -{ - luaL_newmetatable(L, type_name); - /* - * Conventionally, make the metatable point to itself - * in __index. If 'methods' contain a field for __index, - * this is a no-op. - */ - lua_pushvalue(L, -1); - lua_setfield(L, -2, "__index"); - lua_pushstring(L, type_name); - lua_setfield(L, -2, "__metatable"); - luaL_register(L, NULL, methods); - lua_pop(L, 1); -} - struct lua_State * mod_lua_init(struct lua_State *L) { lua_atpanic(L, box_lua_panic); /* box, box.tuple */ - lua_register_type(L, tuplelib_name, lbox_tuple_meta); + tarantool_lua_register_type(L, tuplelib_name, lbox_tuple_meta); luaL_register(L, "box", boxlib); lua_pop(L, 1); /* box.index */ - lua_register_type(L, indexlib_name, lbox_index_meta); + tarantool_lua_register_type(L, indexlib_name, lbox_index_meta); luaL_register(L, "box.index", indexlib); lua_pop(L, 1); /* Load box.lua */ diff --git a/test/box/lua.result b/test/box/lua.result index 2c5f7171dc..0c28179dbd 100644 --- a/test/box/lua.result +++ b/test/box/lua.result @@ -12,6 +12,7 @@ lua print(' lua says: hello') ... lua for n in pairs(box) do print(' - box.', n) end --- + - box.fiber - box.space - box.cfg - box.on_reload_configuration @@ -19,10 +20,10 @@ lua for n in pairs(box) do print(' - box.', n) end - box.process - box.delete - box.insert - - box.replace + - box.select - box.index - box.unpack - - box.select + - box.replace - box.select_range - box.pack ... @@ -520,3 +521,35 @@ one more lua box.space[0]:truncate() --- ... +lua box.fiber.sleep(0) +--- +... +lua box.fiber.sleep(0.01) +--- +... +lua box.fiber.sleep(0.0001) +--- +... +lua box.fiber.sleep('hello') +--- +error: 'Lua error: fiber.sleep(delay): bad arguments' +... +lua box.fiber.sleep(box, 0.001) +--- +error: 'Lua error: fiber.sleep(delay): bad arguments' +... +lua f = box.fiber.self() +--- +... +lua box.fiber.cancel(f) +lua f:id(), box.fiber.self():id() +--- + - 109 + - 109 +... +lua box.fiber.cancel(box.fiber.self()) +lua f:id(), box.fiber.self():id() +--- + - 110 + - 110 +... diff --git a/test/box/lua.test b/test/box/lua.test index 8fcd3fc58f..e887edfc9d 100644 --- a/test/box/lua.test +++ b/test/box/lua.test @@ -142,3 +142,13 @@ exec admin "lua for k, v in t:pairs() do print(v) end" exec admin "lua t=box.space[0]:replace('test', 'another field', 'one more')" exec admin "lua for k, v in t:pairs() do print(v) end" exec admin "lua box.space[0]:truncate()" +exec admin "lua box.fiber.sleep(0)" +exec admin "lua box.fiber.sleep(0.01)" +exec admin "lua box.fiber.sleep(0.0001)" +exec admin "lua box.fiber.sleep('hello')" +exec admin "lua box.fiber.sleep(box, 0.001)" +exec admin "lua f = box.fiber.self()" +exec admin "lua box.fiber.cancel(f)" +exec admin "lua f:id(), box.fiber.self():id()" +exec admin "lua box.fiber.cancel(box.fiber.self())" +exec admin "lua f:id(), box.fiber.self():id()" -- GitLab