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