From 0afa29ab119be4f067c6dafa41385d3325de31c4 Mon Sep 17 00:00:00 2001
From: Roman Tsisyk <roman@tsisyk.com>
Date: Mon, 21 Jul 2014 17:02:00 +0400
Subject: [PATCH] Implement C API for fiber local storage

This patch removes `session` and `lua_storage` fields from struct fiber.
A new API for fiber-local variables is now used for this purpose.
This change solves dependency problems between session and fiber modules.
---
 src/admin.cc              |  2 +-
 src/box/access.h          |  2 +-
 src/box/authentication.cc |  2 +-
 src/box/txn.h             |  3 +-
 src/fiber.cc              | 39 +++++++++++++++++++++--
 src/fiber.h               | 65 ++++++++++++++++++++++++++++++---------
 src/lua/fiber.cc          | 20 +++++++-----
 src/lua/init.cc           |  4 ---
 src/lua/session.cc        | 19 +++++-------
 src/session.cc            | 15 ++++++++-
 src/session.h             | 17 ++++++++++
 11 files changed, 142 insertions(+), 46 deletions(-)

diff --git a/src/admin.cc b/src/admin.cc
index 5cf2331128..3f462e7b5d 100644
--- a/src/admin.cc
+++ b/src/admin.cc
@@ -58,7 +58,7 @@ admin_dispatch(struct ev_io *coio, struct iobuf *iobuf, lua_State *L)
 	char delim[SESSION_DELIM_SIZE + 1];
 	/* \n must folow user-specified delimiter */
 	int delim_len = snprintf(delim, sizeof(delim), "%s\n",
-				 fiber()->session->delim);
+				 session()->delim);
 
 	char *eol;
 	while (in->pos == NULL ||
diff --git a/src/box/access.h b/src/box/access.h
index a101afaf88..6fb011de4e 100644
--- a/src/box/access.h
+++ b/src/box/access.h
@@ -139,7 +139,7 @@ user_by_name(const char *name, uint32_t len);
  */
 #define user()							\
 ({								\
-	struct session *s = fiber()->session;			\
+	struct session *s = session();				\
 	uint8_t auth_token = s ? s->auth_token : (int) ADMIN;	\
 	struct user *u = &users[auth_token];			\
 	assert(u->auth_token == auth_token);			\
diff --git a/src/box/authentication.cc b/src/box/authentication.cc
index 5cedb827b1..89f03d5d47 100644
--- a/src/box/authentication.cc
+++ b/src/box/authentication.cc
@@ -39,7 +39,7 @@ authenticate(const char *user_name, uint32_t len,
 		snprintf(name, sizeof(name), "%.*s", len, user_name);
 		tnt_raise(ClientError, ER_NO_SUCH_USER, name);
 	}
-	struct session *session = fiber()->session;
+	struct session *session = session();
 	uint32_t part_count = mp_decode_array(&tuple);
 	if (part_count < 2) {
 		/* Expected at least: authentication mechanism and data. */
diff --git a/src/box/txn.h b/src/box/txn.h
index 2856abcc47..aebe1259a0 100644
--- a/src/box/txn.h
+++ b/src/box/txn.h
@@ -30,6 +30,7 @@
  */
 #include "index.h"
 #include "trigger.h"
+#include "session.h"
 
 extern double too_long_threshold;
 struct tuple;
@@ -71,7 +72,7 @@ struct txn {
 };
 
 /* Pointer to the current transaction (if any) */
-#define in_txn() (fiber()->session->txn)
+#define in_txn() (session()->txn)
 
 /**
  * Start a new statement. If no current transaction,
diff --git a/src/fiber.cc b/src/fiber.cc
index 7afdc17dc2..ca9d4ffb40 100644
--- a/src/fiber.cc
+++ b/src/fiber.cc
@@ -42,6 +42,14 @@ static struct cord main_cord;
 __thread struct cord *cord_ptr = NULL;
 pthread_t main_thread_id;
 
+/*
+ * Local storage finalizers
+ */
+static struct {
+	fiber_key_gc_cb cb;
+	void *arg;
+} key_gc[FIBER_KEY_MAX]; /* zeroed by linker */
+
 static void
 update_last_stack_frame(struct fiber *fiber)
 {
@@ -364,9 +372,11 @@ fiber_zombificate()
 		fiber_wakeup(fiber->waiter);
 	rlist_del(&fiber->state);
 	fiber->waiter = NULL;
-	fiber->session = NULL;
 	fiber_set_name(fiber, "zombie");
 	fiber->f = NULL;
+#if !defined(NDEBUG)
+	memset(fiber->fls, '#', sizeof(fiber->fls));
+#endif /* !defined(NDEBUG) */
 	unregister_fid(fiber);
 	fiber->fid = 0;
 	fiber->flags = 0;
@@ -397,6 +407,17 @@ fiber_loop(void *data __attribute__((unused)))
 				fiber_name(fiber()));
 			panic("fiber `%s': exiting", fiber_name(fiber()));
 		}
+		for (int i = 0; i < FIBER_KEY_MAX; i++) {
+			enum fiber_key key = (enum fiber_key) i;
+			if (key_gc[key].cb == NULL)
+				continue;
+			try {
+				key_gc[key].cb(key, key_gc[key].arg);
+			} catch(Exception *e) {
+				say_error("exception raised on fiber stop");
+				e->log();
+			}
+		}
 		fiber_zombificate();
 		fiber_yield();	/* give control back to scheduler */
 	}
@@ -415,6 +436,19 @@ fiber_set_name(struct fiber *fiber, const char *name)
 	region_set_name(&fiber->gc, name);
 }
 
+extern inline void
+fiber_set_key(struct fiber *fiber, enum fiber_key key, void *value);
+
+extern inline void *
+fiber_get_key(struct fiber *fiber, enum fiber_key key);
+
+void
+fiber_key_on_gc(enum fiber_key key, fiber_key_gc_cb cb, void *arg)
+{
+	key_gc[key].cb = cb;
+	key_gc[key].arg = arg;
+}
+
 /**
  * Create a new fiber.
  *
@@ -455,10 +489,9 @@ fiber_new(const char *name, void (*f) (va_list))
 	if (++cord->max_fid < 100)
 		cord->max_fid = 100;
 	fiber->fid = cord->max_fid;
-	fiber->session = NULL;
+	memset(fiber->fls, 0, sizeof(fiber->fls)); /* clear local storage */
 	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 6ef4bb1a2e..e0dd4581bb 100644
--- a/src/fiber.h
+++ b/src/fiber.h
@@ -75,6 +75,17 @@ class FiberCancelException: public Exception {
 };
 #endif /* defined(__cplusplus) */
 
+/**
+ * \brief Pre-defined key for fiber local storage
+ */
+enum fiber_key {
+	/** box.session */
+	FIBER_KEY_SESSION = 0,
+	/** Lua fiber.storage */
+	FIBER_KEY_LUA_STORAGE = 1,
+	FIBER_KEY_MAX = 2
+};
+
 struct fiber {
 #ifdef ENABLE_BACKTRACE
 	void *last_stack_frame;
@@ -85,16 +96,6 @@ struct fiber {
 	struct region gc;
 	/** Fiber id. */
 	uint32_t fid;
-	/**
-	 * The logical user session the fiber is running
-	 * on behalf of. The concept of an associated session
-	 * is similar to the concept of controlling tty
-	 * in a UNIX process. When a fiber is created,
-	 * it has no session. If it's running a request on behalf
-	 * of a user connection, it's session is changed
-	 * to represent this connection.
-	 */
-	struct session *session;
 
 	struct rlist link;
 	struct rlist state;
@@ -110,7 +111,7 @@ struct fiber {
 	va_list f_data;
 	uint32_t flags;
 	struct fiber *waiter;
-	int lua_storage;
+	void *fls[FIBER_KEY_MAX]; /* fiber local storage */
 };
 
 enum { FIBER_CALL_STACK = 16 };
@@ -233,13 +234,47 @@ void fiber_sleep(ev_tstamp s);
 struct tbuf;
 void fiber_schedule(ev_watcher *watcher, int event __attribute__((unused)));
 
-/** Set or clear this fiber's session. */
-static inline void
-fiber_set_session(struct fiber *f, struct session *session)
+/**
+ * \brief Associate \a value with \a key in fiber local storage
+ * \param fiber fiber
+ * \param key pre-defined key
+ * \param value value to set
+ */
+inline void
+fiber_set_key(struct fiber *fiber, enum fiber_key key, void *value)
 {
-	f->session = session;
+	assert(key < FIBER_KEY_MAX);
+	fiber->fls[key] = value;
 }
 
+/**
+ * \brief Retrieve value by \a key from fiber local storage
+ * \param fiber fiber
+ * \param key pre-defined key
+ * \return value from from fiber local storage
+ */
+inline void *
+fiber_get_key(struct fiber *fiber, enum fiber_key key)
+{
+	assert(key < FIBER_KEY_MAX);
+	return fiber->fls[key];
+}
+
+/**
+ * Finalizer callback
+ * \sa fiber_key_on_gc()
+ */
+typedef void (*fiber_key_gc_cb)(enum fiber_key key, void *arg);
+
+/**
+ * \brief Set finalizing callback invoked on destroy local storage value
+ * \param key key
+ * \param cb callback
+ * Finalizers are global (i.e. are not cord/thread-local).
+ */
+void
+fiber_key_on_gc(enum fiber_key key, fiber_key_gc_cb cb, void *arg);
+
 typedef int (*fiber_stat_cb)(struct fiber *f, void *ctx);
 
 int fiber_stat(fiber_stat_cb cb, void *cb_ctx);
diff --git a/src/lua/fiber.cc b/src/lua/fiber.cc
index d5724f2b24..5fef49d253 100644
--- a/src/lua/fiber.cc
+++ b/src/lua/fiber.cc
@@ -30,7 +30,6 @@
 
 #include <fiber.h>
 #include "lua/utils.h"
-#include <session.h>
 #include <scoped_guard.h>
 
 extern "C" {
@@ -261,12 +260,13 @@ 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;
+		int storage_ref = (int)(intptr_t)
+			fiber_get_key(fiber(), FIBER_KEY_LUA_STORAGE);
+		if (storage_ref > 0)
+			lua_unref(L, storage_ref);
+		fiber_set_key(fiber(), FIBER_KEY_LUA_STORAGE, NULL);
 	});
 
 	try {
@@ -368,11 +368,15 @@ static int
 lbox_fiber_storage(struct lua_State *L)
 {
 	struct fiber *f = lbox_checkfiber(L, 1);
-	if (f->lua_storage == LUA_NOREF) {
+	int storage_ref = (int)(intptr_t)
+		fiber_get_key(f, FIBER_KEY_LUA_STORAGE);
+	if (storage_ref <= 0) {
 		lua_newtable(L); /* create local storage on demand */
-		f->lua_storage = luaL_ref(L, LUA_REGISTRYINDEX);
+		storage_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+		fiber_set_key(f, FIBER_KEY_LUA_STORAGE,
+			      (void *)(intptr_t) storage_ref);
 	}
-	lua_rawgeti(L, LUA_REGISTRYINDEX, f->lua_storage);
+	lua_rawgeti(L, LUA_REGISTRYINDEX, storage_ref);
 	return 1;
 }
 
diff --git a/src/lua/init.cc b/src/lua/init.cc
index 8ebca87f45..31e13f3305 100644
--- a/src/lua/init.cc
+++ b/src/lua/init.cc
@@ -44,7 +44,6 @@ extern "C" {
 
 
 #include <fiber.h>
-#include <session.h>
 #include <scoped_guard.h>
 #include "coeio.h"
 #include "lua/fiber.h"
@@ -382,9 +381,6 @@ run_script(va_list ap)
 	 */
 	fiber_sleep(0.0);
 
-	/* Create session with ADMIN privileges for interactive mode */
-	SessionGuard session_guard(0, 0);
-
 	if (access(path, F_OK) == 0) {
 		/* Execute script. */
 		if (luaL_loadfile(L, path) != 0)
diff --git a/src/lua/session.cc b/src/lua/session.cc
index 1666f27afb..d5f2dae2fa 100644
--- a/src/lua/session.cc
+++ b/src/lua/session.cc
@@ -54,7 +54,7 @@ static const char *sessionlib_name = "session";
 static int
 lbox_session_id(struct lua_State *L)
 {
-	lua_pushnumber(L, fiber()->session ? fiber()->session->id : 0);
+	lua_pushnumber(L, session()->id);
 	return 1;
 }
 
@@ -62,8 +62,7 @@ lbox_session_id(struct lua_State *L)
 static int
 lbox_session_uid(struct lua_State *L)
 {
-	lua_pushnumber(L, fiber()->session ?
-		       fiber()->session->uid : (int) GUEST);
+	lua_pushnumber(L, session()->uid);
 	return 1;
 }
 
@@ -71,9 +70,7 @@ lbox_session_uid(struct lua_State *L)
 static int
 lbox_session_user(struct lua_State *L)
 {
-	struct user *user = NULL;
-	if (fiber()->session)
-		user = user_cache_find(fiber()->session->uid);
+	struct user *user = user_cache_find(session()->uid);
 	if (user)
 		lua_pushstring(L, user->name);
 	else
@@ -87,7 +84,7 @@ lbox_session_su(struct lua_State *L)
 {
 	if (lua_gettop(L) != 1)
 		luaL_error(L, "session.su(): bad arguments");
-	struct session *session = fiber()->session;
+	struct session *session = session();
 	if (session == NULL)
 		luaL_error(L, "session.su(): session does not exit");
 	struct user *user;
@@ -148,7 +145,7 @@ lbox_session_peer(struct lua_State *L)
 		luaL_error(L, "session.peer(sid): bad arguments");
 
 	uint32_t sid = lua_gettop(L) == 1 ?
-		luaL_checkint(L, -1) : fiber()->session->id;
+		luaL_checkint(L, -1) : session()->id;
 
 	int fd = session_fd(sid);
 	struct sockaddr_storage addr;
@@ -162,12 +159,12 @@ lbox_session_peer(struct lua_State *L)
 static int
 lbox_session_delimiter(struct lua_State *L)
 {
-	if (fiber()->session == NULL)
+	if (session() == NULL)
 		luaL_error(L, "session.delimiter(): session does not exit");
 
 	if (lua_gettop(L) < 1) {
 		/* Get delimiter */
-		lua_pushstring(L, fiber()->session->delim);
+		lua_pushstring(L, session()->delim);
 		return 1;
 	}
 
@@ -175,7 +172,7 @@ lbox_session_delimiter(struct lua_State *L)
 	if (lua_type(L, 1) != LUA_TSTRING)
 		luaL_error(L, "session.delimiter(string): expected a string");
 
-	snprintf(fiber()->session->delim, SESSION_DELIM_SIZE, "%s",
+	snprintf(session()->delim, SESSION_DELIM_SIZE, "%s",
 		 lua_tostring(L, 1));
 	return 0;
 }
diff --git a/src/session.cc b/src/session.cc
index 788933811d..e7c0941b2e 100644
--- a/src/session.cc
+++ b/src/session.cc
@@ -126,12 +126,23 @@ session_fd(uint32_t sid)
 	return session->fd;
 }
 
+static void
+fiber_key_session_gc(enum fiber_key key, void *arg)
+{
+	(void) arg;
+	struct session *session = (struct session *) fiber_get_key(fiber(), key);
+	if (session == NULL)
+		return;
+	session_destroy(session);
+}
+
 void
 session_init()
 {
 	session_registry = mh_i32ptr_new();
 	if (session_registry == NULL)
 		panic("out of memory");
+	fiber_key_on_gc(FIBER_KEY_SESSION, fiber_key_session_gc, NULL);
 	mempool_create(&session_pool, &cord()->slabc, sizeof(struct session));
 }
 
@@ -150,8 +161,10 @@ SessionGuard::SessionGuard(int fd, uint64_t cookie)
 
 SessionGuard::~SessionGuard()
 {
-	assert(session == fiber()->session);
+	assert(session == (struct session *) fiber_get_key(fiber(),
+							   FIBER_KEY_SESSION));
 	session_destroy(session);
+	fiber_set_session(fiber(), NULL);
 }
 
 SessionGuardWithTriggers::SessionGuardWithTriggers(int fd, uint64_t cookie)
diff --git a/src/session.h b/src/session.h
index 8595c505bb..1058016117 100644
--- a/src/session.h
+++ b/src/session.h
@@ -31,6 +31,7 @@
 #include <inttypes.h>
 #include <stdbool.h>
 #include "trigger.h"
+#include "fiber.h"
 
 enum {	SESSION_SEED_SIZE = 32, SESSION_DELIM_SIZE = 16 };
 /** Predefined user ids. */
@@ -138,6 +139,22 @@ session_free();
 void
 session_storage_cleanup(int sid);
 
+static inline void
+fiber_set_session(struct fiber *fiber, struct session *session)
+{
+	fiber_set_key(fiber, FIBER_KEY_SESSION, session);
+}
+
+#define session() ({\
+	struct session *s = (struct session *) fiber_get_key(fiber(),		\
+		FIBER_KEY_SESSION);						\
+	/* Create session on demand */						\
+	if (s == NULL) {							\
+		s = session_create(-1, 0);					\
+		fiber_set_session(fiber(), s);					\
+	}									\
+	s; })
+
 /** A helper class to create and set session in single-session fibers. */
 struct SessionGuard
 {
-- 
GitLab