From a467d2feb2b102d4a17859b75dd0aa5eda385732 Mon Sep 17 00:00:00 2001 From: Roman Tsisyk <roman@tsisyk.com> Date: Fri, 18 Jul 2014 17:44:30 +0400 Subject: [PATCH] Fix #387: fiber local storage in Lua --- src/fiber.cc | 1 + src/fiber.h | 1 + src/lua/fiber.cc | 35 ++++++++++++ test/box/fiber.result | 117 ++++++++++++++++++++++++++++++++++++++++ test/box/fiber.test.lua | 63 ++++++++++++++++++++++ 5 files changed, 217 insertions(+) diff --git a/src/fiber.cc b/src/fiber.cc index dfed60327a..7afdc17dc2 100644 --- a/src/fiber.cc +++ b/src/fiber.cc @@ -458,6 +458,7 @@ fiber_new(const char *name, void (*f) (va_list)) fiber->session = NULL; fiber->flags = 0; fiber->waiter = NULL; + fiber->lua_storage = -2; /* LUA_NOREF */; fiber_set_name(fiber, name); register_fid(fiber); diff --git a/src/fiber.h b/src/fiber.h index 0dd014c8d7..89de53f622 100644 --- a/src/fiber.h +++ b/src/fiber.h @@ -111,6 +111,7 @@ struct fiber { uint32_t flags; struct fiber *waiter; uint64_t cookie; + int lua_storage; }; enum { FIBER_CALL_STACK = 16 }; diff --git a/src/lua/fiber.cc b/src/lua/fiber.cc index ba69d6ac00..d5724f2b24 100644 --- a/src/lua/fiber.cc +++ b/src/lua/fiber.cc @@ -31,6 +31,7 @@ #include <fiber.h> #include "lua/utils.h" #include <session.h> +#include <scoped_guard.h> extern "C" { #include <lua.h> @@ -261,6 +262,12 @@ box_lua_fiber_run_detached(va_list ap) LuarefGuard coro_guard(va_arg(ap, int)); struct lua_State *L = va_arg(ap, struct lua_State *); SessionGuard session_guard(-1, 0); + auto storage_guard = make_scoped_guard([=] { + /* Destroy local storage */ + if (fiber()->lua_storage != LUA_NOREF) + lua_unref(L, fiber()->lua_storage); + fiber()->lua_storage = LUA_NOREF; + }); try { lbox_call(L, lua_gettop(L) - 1, LUA_MULTRET); @@ -357,6 +364,33 @@ lbox_fiber_name(struct lua_State *L) } } +static int +lbox_fiber_storage(struct lua_State *L) +{ + struct fiber *f = lbox_checkfiber(L, 1); + if (f->lua_storage == LUA_NOREF) { + lua_newtable(L); /* create local storage on demand */ + f->lua_storage = luaL_ref(L, LUA_REGISTRYINDEX); + } + lua_rawgeti(L, LUA_REGISTRYINDEX, f->lua_storage); + return 1; +} + +static int +lbox_fiber_index(struct lua_State *L) +{ + if (lua_gettop(L) < 2) + return 0; + if (lua_isstring(L, 2) && strcmp(lua_tostring(L, 2), "storage") == 0) + return lbox_fiber_storage(L); + + /* Get value from metatable */ + lua_getmetatable(L, 1); + lua_pushvalue(L, 2); + lua_gettable(L, -2); + return 1; +} + /** * Yield to the sched fiber and sleep. * @param[in] amount of time to sleep (double) @@ -456,6 +490,7 @@ static const struct luaL_reg lbox_fiber_meta [] = { {"cancel", lbox_fiber_cancel}, {"status", lbox_fiber_status}, {"testcancel", lbox_fiber_testcancel}, + {"__index", lbox_fiber_index}, {"__gc", lbox_fiber_gc}, {NULL, NULL} }; diff --git a/test/box/fiber.result b/test/box/fiber.result index 7d6971c765..606b86e50b 100644 --- a/test/box/fiber.result +++ b/test/box/fiber.result @@ -591,6 +591,123 @@ fiber.find(fib_id) --- - null ... +-- +-- Test local storage +-- +fiber.self().storage +--- +- [] +... +fiber.self().storage.key = 48 +--- +... +fiber.self().storage.key +--- +- 48 +... +--# setopt delimiter ';' +function testfun(ch) + while fiber.self().storage.key == nil do + print('wait') + fiber.sleep(0) + end + ch:put(fiber.self().storage.key) +end; +--- +... +--# setopt delimiter '' +ch = fiber.channel(1) +--- +... +f = fiber.create(testfun, ch) +--- +... +f.storage.key = 'some value' +--- +... +ch:get() +--- +- some value +... +ch:close() +--- +... +ch = nil +--- +... +fiber.self().storage.key -- our local storage is not affected by f +--- +- 48 +... +-- attempt to access local storage of dead fiber raises error +pcall(function(f) return f.storage end, f) +--- +- false +- '[string "return pcall(function(f) return f.storage end..."]:1: the fiber is dead' +... +-- +-- Test that local storage is garbage collected when fiber is died +-- +ffi = require('ffi') +--- +... +ch = fiber.channel(1) +--- +... +--# setopt delimiter ';' +function testfun() + fiber.self().storage.x = ffi.gc(ffi.new('char[1]'), + function() ch:put('gc ok') end) +end; +--- +... +--# setopt delimiter '' +f = fiber.create(testfun) +--- +... +collectgarbage('collect') +--- +- 0 +... +ch:get() +--- +- gc ok +... +ch:close() +--- +... +ch = nil +--- +... +-- +-- Test that local storage is not garbage collected with fiber object +-- +--# setopt delimiter ';' +function testfun(ch) + fiber.self().storage.x = 'ok' + collectgarbage('collect') + ch:put(fiber.self().storage.x or 'failed') +end; +--- +... +--# setopt delimiter '' +ch = fiber.channel(1) +--- +... +fiber.create(testfun, ch):status() +--- +- dead +... +ch:get() +--- +- ok +... +ch:close() +--- +... +ch = nil +--- +... fiber = nil --- ... diff --git a/test/box/fiber.test.lua b/test/box/fiber.test.lua index 14078a4226..bb5802db31 100644 --- a/test/box/fiber.test.lua +++ b/test/box/fiber.test.lua @@ -212,4 +212,67 @@ f:cancel() fib_id = fiber.create(testfun):id() fiber.find(fib_id):cancel() fiber.find(fib_id) + +-- +-- Test local storage +-- + +fiber.self().storage +fiber.self().storage.key = 48 +fiber.self().storage.key + +--# setopt delimiter ';' +function testfun(ch) + while fiber.self().storage.key == nil do + print('wait') + fiber.sleep(0) + end + ch:put(fiber.self().storage.key) +end; +--# setopt delimiter '' +ch = fiber.channel(1) +f = fiber.create(testfun, ch) +f.storage.key = 'some value' +ch:get() +ch:close() +ch = nil +fiber.self().storage.key -- our local storage is not affected by f +-- attempt to access local storage of dead fiber raises error +pcall(function(f) return f.storage end, f) + +-- +-- Test that local storage is garbage collected when fiber is died +-- +ffi = require('ffi') +ch = fiber.channel(1) +--# setopt delimiter ';' +function testfun() + fiber.self().storage.x = ffi.gc(ffi.new('char[1]'), + function() ch:put('gc ok') end) +end; +--# setopt delimiter '' +f = fiber.create(testfun) +collectgarbage('collect') +ch:get() +ch:close() +ch = nil + + + +-- +-- Test that local storage is not garbage collected with fiber object +-- +--# setopt delimiter ';' +function testfun(ch) + fiber.self().storage.x = 'ok' + collectgarbage('collect') + ch:put(fiber.self().storage.x or 'failed') +end; +--# setopt delimiter '' +ch = fiber.channel(1) +fiber.create(testfun, ch):status() +ch:get() +ch:close() +ch = nil + fiber = nil -- GitLab