trafficserver-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jpe...@apache.org
Subject git commit: TS-1634: reimplement Lua state management to support reload
Date Sat, 23 Feb 2013 05:31:35 GMT
Updated Branches:
  refs/heads/master 07ff39981 -> 02414852f


TS-1634: reimplement Lua state management to support reload


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/02414852
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/02414852
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/02414852

Branch: refs/heads/master
Commit: 02414852f57907469d4cced9bf93f4d95900261f
Parents: 07ff399
Author: James Peach <jpeach@apache.org>
Authored: Tue Jan 22 21:27:30 2013 -0800
Committer: James Peach <jpeach@apache.org>
Committed: Fri Feb 22 21:27:57 2013 -0800

----------------------------------------------------------------------
 CHANGES                              |    2 +
 plugins/experimental/lua/Makefile.am |    2 +-
 plugins/experimental/lua/hook.cc     |  299 ++++++++++++--------------
 plugins/experimental/lua/hook.h      |   12 +-
 plugins/experimental/lua/lutil.cc    |   72 +-----
 plugins/experimental/lua/lutil.h     |   63 +-----
 plugins/experimental/lua/plugin.cc   |   25 +--
 plugins/experimental/lua/remap.cc    |   90 +++------
 plugins/experimental/lua/state.cc    |  333 +++++++++++++++++++++++++++++
 plugins/experimental/lua/state.h     |  157 ++++++++++++++
 10 files changed, 694 insertions(+), 361 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/02414852/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index 541cea0..8745fd0 100644
--- a/CHANGES
+++ b/CHANGES
@@ -2,6 +2,8 @@
 Changes with Apache Traffic Server 3.3.1
 
 
+  *) [TS-1634] reimplement Lua state management to support reload
+
   *) [TS-1716] authproxy fails to reserve an argument index in global mode
 
   *) [TS-1710] esi plugin enhancement such as support forward proxy

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/02414852/plugins/experimental/lua/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/lua/Makefile.am b/plugins/experimental/lua/Makefile.am
index 016e597..f384720 100644
--- a/plugins/experimental/lua/Makefile.am
+++ b/plugins/experimental/lua/Makefile.am
@@ -30,7 +30,7 @@ lua_la_CPPFLAGS = \
 pkglib_LTLIBRARIES = lua.la
 
 lua_la_LIBADD = ${LUA_LIBS}
-lua_la_SOURCES = remap.cc plugin.cc lapi.cc lutil.cc lconfig.cc hook.cc
+lua_la_SOURCES = remap.cc plugin.cc lapi.cc lutil.cc lconfig.cc hook.cc state.cc
 lua_la_LDFLAGS = -module -avoid-version -shared
 
 endif

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/02414852/plugins/experimental/lua/hook.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/lua/hook.cc b/plugins/experimental/lua/hook.cc
index 7b8ed66..83e8fae 100644
--- a/plugins/experimental/lua/hook.cc
+++ b/plugins/experimental/lua/hook.cc
@@ -22,21 +22,11 @@
 #include "lapi.h"
 #include "lutil.h"
 #include "hook.h"
+#include "state.h"
 
 #include <memory> // placement new
 #include <ink_config.h>
 
-typedef TSCont HookDemuxTable[TS_HTTP_LAST_HOOK];
-
-// Continuation tables for global, txn and ssn hooks. These are all indexed by the TSHttpHookID and
-// are used to select which callback to invoke during event demuxing.
-static struct
-{
-  HookDemuxTable global;
-  HookDemuxTable txn;
-  HookDemuxTable ssn;
-} HttpHookDemuxTable;
-
 const char *
 HttpHookName(TSHttpHookID hookid)
 {
@@ -104,31 +94,74 @@ LuaPushEventData(lua_State * lua, TSEvent event, void * edata)
   }
 }
 
-// The per-ssn and per-txn argument mechanism stores a pointer, so it's NULL when not set. Unfortunately, 0 is a
-// legitimate Lua reference value (all valies except LUA_NOREF are legitimate), so we can't distinguish NULL from a 0
-// reference. In 64-bit mode we have some extra bits and we can maintain the state, but in 32-bit mode, we need to
-// allocate the LuaHookReference to have enough space to store the state.
-
-union LuaHookReference
-{
-  struct ref {
-    bool  set;
-    int   value;
-  } ref;
-  void * storage;
-};
 
 // For 64-bit pointers, we can inline the LuaHookReference, otherwise we need an extra malloc.
+//
 #if SIZEOF_VOID_POINTER >= 8
 #define INLINE_LUA_HOOK_REFERENCE 1
 #else
 #undef INLINE_LUA_HOOK_REFERENCE
 #endif
 
-// Verify that LuaHookReference fits in sizeof(void *).
-#if INLINE_LUA_HOOK_REFERENCE
-extern char __LuaHookReferenceSizeCheck[sizeof(LuaHookReference) == SIZEOF_VOID_POINTER ? 0 : -1];
+template <typename t1, typename t2>
+struct inline_tuple
+{
+  typedef t1 first_type;
+  typedef t2 second_type;
+  typedef inline_tuple<first_type, second_type> this_type;
+
+  union {
+    struct {
+      first_type first;
+      second_type second;
+    } s;
+    void * ptr;
+  } storage;
+
+  first_type& first() { return storage.s.first; }
+  second_type& second() { return storage.s.second; }
+
+  static void * allocate(const first_type first, const second_type second) {
+#if defined(INLINE_LUA_HOOK_REFERENCE)
+    typedef char __size_check[sizeof(this_type) == sizeof(void *) ? 0 : -1];
+
+    this_type obj;
+    obj.first() = first;
+    obj.second() = second;
+    return obj.storage.ptr;
+#else
+    this_type * ptr = (this_type *)TSmalloc(sizeof(this_type));
+    ptr->first() = first;
+    ptr->second() = second;
+    return ptr;
+#endif
+  }
+
+  static void free(void * ptr) {
+#if defined(INLINE_LUA_HOOK_REFERENCE)
+    // Nothing to do, because we never allocated.
+#else
+    TSfree(ptr);
+#endif
+  }
+
+  static this_type get(void * ptr) {
+#if defined(INLINE_LUA_HOOK_REFERENCE)
+    this_type obj;
+    obj.storage.ptr = ptr;
+    return obj;
+#else
+    return ptr ? *(this_type *)ptr : this_type();
 #endif
+  }
+
+};
+
+// The per-ssn and per-txn argument mechanism stores a pointer, so it's NULL when not set. Unfortunately, 0 is a
+// legitimate Lua reference value (all values except LUA_NOREF are legitimate), so we can't distinguish NULL from a 0
+// reference. In 64-bit mode we have some extra bits and we can maintain the state, but in 32-bit mode, we need to
+// allocate the LuaHookReference to have enough space to store the state.
+typedef inline_tuple<int, bool> LuaHookReference;
 
 static void *
 LuaHttpObjectArgGet(TSHttpSsn ssn)
@@ -157,48 +190,22 @@ LuaHttpObjectArgSet(TSHttpTxn txn, void * ptr)
 template<typename T> static int
 LuaGetArgReference(T ptr)
 {
-  LuaHookReference href;
-
-  href.storage = LuaHttpObjectArgGet(ptr);
-
-#if !defined(INLINE_LUA_HOOK_REFERENCE)
-  if (href.storage) {
-    href  = *(LuaHookReference *)href.storage;
-  }
-#endif
-
-  return (href.ref.set) ? href.ref.value : LUA_NOREF;
+  LuaHookReference href(LuaHookReference::get(LuaHttpObjectArgGet(ptr)));
+  // Only return the Lua ref if it was previously set.
+  return href.second() ? href.first() : LUA_NOREF;
 }
 
 template <typename T> void
 LuaSetArgReference(T ptr, int ref)
 {
-  LuaHookReference href;
-
-  href.storage = NULL;
-  href.ref.value = ref;
-  href.ref.set = true;
-
-#if defined(INLINE_LUA_HOOK_REFERENCE)
-  LuaHttpObjectArgSet(ptr, href.storage);
-#else
-  LuaHookReference * tmp = (LuaHookReference *)LuaHttpObjectArgGet(ptr);
-  if (tmp) {
-    *tmp = href;
-  } else {
-    tmp = (LuaHookReference *)TSmalloc(sizeof(LuaHookReference));
-    *tmp = href;
-    LuaHttpObjectArgSet(ptr, tmp);
-  }
-#endif
+  LuaHookReference::free(LuaHttpObjectArgGet(ptr));
+  LuaHttpObjectArgSet(ptr, LuaHookReference::allocate(ref, true));
 }
 
 template <typename T> static void
 LuaClearArgReference(T ptr)
 {
-#if !defined(INLINE_LUA_HOOK_REFERENCE)
-  TSfree(LuaHttpObjectArgGet(ptr));
-#endif
+  LuaHookReference::free(LuaHttpObjectArgGet(ptr));
   LuaHttpObjectArgSet(ptr, NULL);
 }
 
@@ -206,31 +213,6 @@ LuaClearArgReference(T ptr)
 template void LuaSetArgReference<TSHttpSsn>(TSHttpSsn ssn, int ref);
 template void LuaSetArgReference<TSHttpTxn>(TSHttpTxn txn, int ref);
 
-static LuaThreadInstance *
-LuaDemuxThreadInstance()
-{
-  LuaThreadInstance * lthread;
-
-  lthread = LuaGetThreadInstance();
-
-  if (lthread == NULL) {
-    lthread = tsnew<LuaThreadInstance>();
-    lthread->lua = LuaPluginNewState();
-    LuaSetThreadInstance(lthread);
-    LuaPluginLoad(lthread->lua, LuaPlugin);
-  }
-
-  return lthread;
-}
-
-static TSHttpHookID
-LuaDemuxHookID(TSCont cont)
-{
-  TSHttpHookID hookid = (TSHttpHookID)(intptr_t)TSContDataGet(cont);
-  TSAssert(HookIsValid(hookid));
-  return hookid;
-}
-
 static void
 LuaDemuxInvokeCallback(lua_State * lua, TSHttpHookID hookid, TSEvent event, void * edata, int ref)
 {
@@ -262,9 +244,9 @@ LuaDemuxInvokeCallback(lua_State * lua, TSHttpHookID hookid, TSEvent event, void
       TSReleaseAssert(0);
   }
 
-  // The item on the top of the stack *ought* to be the callback function. However when we register a cleanup function
-  // to release the callback reference (because the ssn ot txn closes), then we won't have a function because there's
-  // nothing to do here.
+  // The item on the top of the stack *ought* to be the callback function. However when we register a
+  // cleanup function to release the callback reference (because the ssn or txn closes), then we won't
+  // have a function because there's nothing to do here.
   if (!lua_isnil(lua, -1)) {
 
     TSAssert(lua_isfunction(lua, -1));
@@ -282,61 +264,63 @@ LuaDemuxInvokeCallback(lua_State * lua, TSHttpHookID hookid, TSEvent event, void
   lua_pop(lua, lua_gettop(lua) - nitems);
 }
 
-static int
-LuaDemuxGlobalHook(TSCont cont, TSEvent event, void * edata)
+int
+LuaDemuxGlobalHook(TSHttpHookID hookid, TSCont cont, TSEvent event, void * edata)
 {
-  TSHttpHookID        hookid = LuaDemuxHookID(cont);
-  LuaThreadInstance * lthread = LuaDemuxThreadInstance();
-  int                 ref = lthread->hooks[hookid];
+  instanceid_t        instanceid = (uintptr_t)TSContDataGet(cont);
+  ScopedLuaState      lstate(instanceid);
+  int                 ref = lstate->hookrefs[hookid];
 
-  LuaLogDebug("%s lthread=%p event=%d edata=%p, ref=%d",
-      HttpHookName(hookid), lthread, event, edata, ref);
+  LuaLogDebug("%u/%p %s event=%d edata=%p, ref=%d",
+      instanceid, lstate->lua,
+      HttpHookName(hookid), event, edata, ref);
 
   if (ref == LUA_NOREF) {
     LuaLogError("no Lua callback for hook %s", HttpHookName(hookid));
     return TS_EVENT_ERROR;
   }
 
-  LuaDemuxInvokeCallback(lthread->lua, hookid, event, edata, ref);
+  LuaDemuxInvokeCallback(lstate->lua, hookid, event, edata, ref);
   return TS_EVENT_NONE;
 }
 
-static int
-LuaDemuxTxnHook(TSCont cont, TSEvent event, void * edata)
+int
+LuaDemuxTxnHook(TSHttpHookID hookid, TSCont cont, TSEvent event, void * edata)
 {
-  TSHttpHookID        hookid = LuaDemuxHookID(cont);
-  LuaThreadInstance * lthread = LuaDemuxThreadInstance();
   int                 ref = LuaGetArgReference((TSHttpTxn)edata);
+  instanceid_t        instanceid = (uintptr_t)TSContDataGet(cont);
+  ScopedLuaState      lstate(instanceid);
 
-  LuaLogDebug("%s(%s) lthread=%p event=%d edata=%p",
-      __func__, HttpHookName(hookid), lthread, event, edata);
+  LuaLogDebug("%s(%s) instanceid=%u event=%d edata=%p",
+      __func__, HttpHookName(hookid), instanceid, event, edata);
 
   if (ref == LUA_NOREF) {
     LuaLogError("no Lua callback for hook %s", HttpHookName(hookid));
     return TS_EVENT_ERROR;
   }
 
-  LuaDemuxInvokeCallback(lthread->lua, hookid, event, edata, ref);
+  LuaDemuxInvokeCallback(lstate->lua, hookid, event, edata, ref);
 
   if (event == TS_EVENT_HTTP_TXN_CLOSE) {
     LuaLogDebug("unref event handler %d", ref);
-    luaL_unref(lthread->lua, LUA_REGISTRYINDEX, ref);
+    luaL_unref(lstate->lua, LUA_REGISTRYINDEX, ref);
     LuaClearArgReference((TSHttpTxn)edata);
   }
 
   return TS_EVENT_NONE;
 }
 
-static int
-LuaDemuxSsnHook(TSCont cont, TSEvent event, void * edata)
+int
+LuaDemuxSsnHook(TSHttpHookID hookid, TSCont cont, TSEvent event, void * edata)
 {
-  TSHttpHookID        hookid = LuaDemuxHookID(cont);
-  LuaThreadInstance * lthread = LuaDemuxThreadInstance();
+  instanceid_t        instanceid = (uintptr_t)TSContDataGet(cont);
+  ScopedLuaState      lstate(instanceid);
   TSHttpSsn           ssn;
   int                 ref;
 
-  // The edata might be a Txn or a Ssn, depending on the event type. If we get here, it's because we registered a
-  // callback on the Ssn, so we need to get back to the Ssn object in order to the the callback table reference ...
+  // The edata might be a Txn or a Ssn, depending on the event type. If we get here, it's because we
+  // registered a callback on the Ssn, so we need to get back to the Ssn object in order to the the
+  // callback table reference ...
   switch (event) {
     case TS_EVENT_HTTP_SSN_START:
     case TS_EVENT_HTTP_SSN_CLOSE:
@@ -346,8 +330,8 @@ LuaDemuxSsnHook(TSCont cont, TSEvent event, void * edata)
       ssn = TSHttpTxnSsnGet((TSHttpTxn)edata);
   }
 
-  LuaLogDebug("%s(%s) lthread=%p event=%d edata=%p",
-      __func__, HttpHookName(hookid), lthread, event, edata);
+  LuaLogDebug("%s(%s) instanceid=%u event=%d edata=%p",
+      __func__, HttpHookName(hookid), instanceid, event, edata);
 
   ref = LuaGetArgReference(ssn);
   if (ref == LUA_NOREF) {
@@ -355,11 +339,11 @@ LuaDemuxSsnHook(TSCont cont, TSEvent event, void * edata)
     return TS_EVENT_ERROR;
   }
 
-  LuaDemuxInvokeCallback(lthread->lua, hookid, event, edata, ref);
+  LuaDemuxInvokeCallback(lstate->lua, hookid, event, edata, ref);
 
   if (event == TS_EVENT_HTTP_SSN_CLOSE) {
     LuaLogDebug("unref event handler %d", ref);
-    luaL_unref(lthread->lua, LUA_REGISTRYINDEX, ref);
+    luaL_unref(lstate->lua, LUA_REGISTRYINDEX, ref);
     LuaClearArgReference((TSHttpSsn)edata);
   }
 
@@ -369,8 +353,8 @@ LuaDemuxSsnHook(TSCont cont, TSEvent event, void * edata)
 bool
 LuaRegisterHttpHooks(lua_State * lua, void * obj, LuaHookAddFunction add, int hooks)
 {
-  bool hooked_close = false;
-  const TSHttpHookID closehook = (add == LuaHttpSsnHookAdd ? TS_HTTP_SSN_CLOSE_HOOK : TS_HTTP_TXN_CLOSE_HOOK);
+  bool                hooked_close = false;
+  const TSHttpHookID  closehook = (add == LuaHttpSsnHookAdd ? TS_HTTP_SSN_CLOSE_HOOK : TS_HTTP_TXN_CLOSE_HOOK);
 
   TSAssert(add == LuaHttpSsnHookAdd || add == LuaHttpTxnHookAdd);
 
@@ -380,6 +364,9 @@ LuaRegisterHttpHooks(lua_State * lua, void * obj, LuaHookAddFunction add, int ho
   // The value on the top of the stack (index -1) MUST be the callback table.
   TSAssert(lua_istable(lua, lua_gettop(lua)));
 
+  // Now we need our LuaThreadState to access the hook tables.
+  ScopedLuaState lstate(lua);
+
   // Walk the table and register the hook for each entry.
   lua_pushnil(lua);  // Push the first key, makes the callback table index -2.
   while (lua_next(lua, -2) != 0) {
@@ -388,8 +375,8 @@ LuaRegisterHttpHooks(lua_State * lua, void * obj, LuaHookAddFunction add, int ho
     // uses 'key' (at index -2) and 'value' (at index -1).
     // LuaLogDebug("key=%s value=%s\n", ltypeof(lua, -2), ltypeof(lua, -1));
 
-    // Now the key (index -2) and value (index -1) got pushed onto the stack. The key must be a hook ID and the value
-    // must be a callback function.
+    // Now the key (index -2) and value (index -1) got pushed onto the stack. The key must be a hook ID and
+    // the value must be a callback function.
     luaL_checktype(lua, -1, LUA_TFUNCTION);
     hookid = (TSHttpHookID)luaL_checkint(lua, -2);
 
@@ -403,7 +390,7 @@ LuaRegisterHttpHooks(lua_State * lua, void * obj, LuaHookAddFunction add, int ho
     }
 
     // At demux time, we need the hook ID and the table (or function) ref.
-    add(obj, hookid);
+    add(obj, lstate.instance(), hookid);
     LuaLogDebug("registered callback table %d for event %s on object %p",
         hooks, HttpHookName(hookid), obj);
 
@@ -412,34 +399,32 @@ next:
     lua_pop(lua, 1);
   }
 
-  // we always need to hook the close because we keep a reference to the callback table and we need to release that
-  // reference when the object's lifetime ends.
+  // we always need to hook the close because we keep a reference to the callback table and we need to
+  // release that reference when the object's lifetime ends.
   if (!hooked_close) {
-    add(obj, closehook);
+    add(obj, lstate.instance(), closehook);
   }
 
   return true;
 }
 
 void
-LuaHttpSsnHookAdd(void * ssn, TSHttpHookID hookid)
+LuaHttpSsnHookAdd(void * ssn, const LuaPluginInstance * instance, TSHttpHookID hookid)
 {
-  TSHttpSsnHookAdd((TSHttpSsn)ssn, hookid, HttpHookDemuxTable.ssn[hookid]);
+  TSHttpSsnHookAdd((TSHttpSsn)ssn, hookid, instance->demux.ssn[hookid]);
 }
 
 void
-LuaHttpTxnHookAdd(void * txn, TSHttpHookID hookid)
+LuaHttpTxnHookAdd(void * txn, const LuaPluginInstance * instance, TSHttpHookID hookid)
 {
-  TSHttpTxnHookAdd((TSHttpTxn)txn, hookid, HttpHookDemuxTable.txn[hookid]);
+  TSHttpTxnHookAdd((TSHttpTxn)txn, hookid, instance->demux.txn[hookid]);
 }
 
 static int
 TSLuaHttpHookRegister(lua_State * lua)
 {
-  TSHttpHookID hookid;
-  LuaThreadInstance * lthread;
+  TSHttpHookID      hookid;
 
-  LuaLogDebug("[1]=%s [2]=%s", ltypeof(lua, 1), ltypeof(lua, 2));
   hookid = (TSHttpHookID)luaL_checkint(lua, 1);
   luaL_checktype(lua, 2, LUA_TFUNCTION);
 
@@ -449,33 +434,38 @@ TSLuaHttpHookRegister(lua_State * lua)
     return -1;
   }
 
-  lthread = LuaGetThreadInstance();
-  if (lthread == NULL) {
-    lthread = tsnew<LuaThreadInstance>();
-    lthread->lua = LuaPluginNewState(LuaPlugin);
-    LuaSetThreadInstance(lthread);
-  }
+  ScopedLuaState lstate(lua);
+  TSReleaseAssert(lstate);
 
-  // Global hooks can only be registered once, but we load the Lua scripts in every thread. Check whether the hook has
-  // already been registered and ignore any double-registrations.
-  if (lthread->hooks[hookid] != LUA_NOREF) {
-    TSReleaseAssert(HttpHookDemuxTable.global[hookid] != NULL);
+  // The lstate must match the current Lua state or something is seriously wrong.
+  TSReleaseAssert(lstate->lua == lua);
+
+  // Global hooks can only be registered once, but we load the Lua scripts in every thread. Check whether
+  // the hook has already been registered and ignore any double-registrations.
+  if (lstate->hookrefs[hookid] != LUA_NOREF) {
+    LuaLogDebug("ignoring double registration for %s hook", HttpHookName(hookid));
     return 0;
   }
 
-  lthread->hooks[hookid] = luaL_ref(lua, LUA_REGISTRYINDEX);
+  // The callback function for the hook should be on the top of the stack now. Keep a reference
+  // to the callback function in the registry so we can pop it out later.
+  TSAssert(lua_type(lua, lua_gettop(lua)) == LUA_TFUNCTION);
+  lstate->hookrefs[hookid] = luaL_ref(lua, LUA_REGISTRYINDEX);
+
+  LuaLogDebug("%u/%p added hook ref %d for %s",
+      lstate->instance->instanceid, lua, lstate->hookrefs[hookid], HttpHookName(hookid));
 
-  if (HttpHookDemuxTable.global[hookid] == NULL) {
-    TSCont cont;
+  // We need to atomically install this global hook. We snaffle the high bit to mark whether or
+  // not it has been installed.
+  if (((uintptr_t)lstate->instance->demux.global[hookid] & 0x01u) == 0) {
+    TSCont cont = (TSCont)((uintptr_t)lstate->instance->demux.global[hookid] | 0x01u);
 
-    cont = TSContCreate(LuaDemuxGlobalHook, TSMutexCreate());
-    if (__sync_bool_compare_and_swap(&HttpHookDemuxTable.global[hookid], NULL, cont)) {
+    if (__sync_bool_compare_and_swap(&lstate->instance->demux.global[hookid],
+          lstate->instance->demux.global[hookid], cont)) {
       LuaLogDebug("installed continuation for %s", HttpHookName(hookid));
-      TSContDataSet(cont, (void *)hookid);
-      TSHttpHookAdd(hookid, cont);
+      TSHttpHookAdd(hookid, (TSCont)((uintptr_t)cont & ~0x01u));
     } else {
       LuaLogDebug("lost hook creation race for %s", HttpHookName(hookid));
-      TSContDestroy(cont);
     }
   }
 
@@ -499,21 +489,10 @@ LuaHookApiInit(lua_State * lua)
   luaL_register(lua, NULL, LUAEXPORTS);
 
   for (unsigned i = 0; i < TS_HTTP_LAST_HOOK; ++i) {
-    if (HttpHookName((TSHttpHookID)i) == NULL) {
-      // Unsupported hook, skip it.
-      continue;
+    if (HttpHookName((TSHttpHookID)i) != NULL) {
+      // Register named constants for each hook ID.
+      LuaSetConstantField(lua, HttpHookName((TSHttpHookID)i), i);
     }
-
-    // Register named constants for each hook ID.
-    LuaSetConstantField(lua, HttpHookName((TSHttpHookID)i), i);
-    // Allocate txn and ssn continuations.
-    HttpHookDemuxTable.txn[i] = TSContCreate(LuaDemuxTxnHook, NULL);
-    HttpHookDemuxTable.ssn[i] = TSContCreate(LuaDemuxSsnHook, NULL);
-    // And keep track of which hook each continuation was allocated for.
-    TSContDataSet(HttpHookDemuxTable.txn[i], (void *)(uintptr_t)i);
-    TSContDataSet(HttpHookDemuxTable.ssn[i], (void *)(uintptr_t)i);
-
-    // Note that we allocate the global continuation table lazily so that we know when to add the hook.
   }
 
   return 1;

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/02414852/plugins/experimental/lua/hook.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/lua/hook.h b/plugins/experimental/lua/hook.h
index 58ea4ac..8c3f642 100644
--- a/plugins/experimental/lua/hook.h
+++ b/plugins/experimental/lua/hook.h
@@ -19,15 +19,21 @@
 #ifndef LUA_HOOK_H_
 #define LUA_HOOK_H_
 
+struct LuaPluginInstance;
+
 // Pointer to LuaHttpSsnHookAdd() or LuaHttpTxnsnHookAdd().
-typedef void (*LuaHookAddFunction)(void *, TSHttpHookID);
+typedef void (*LuaHookAddFunction)(void *, const LuaPluginInstance *, TSHttpHookID);
 
-void LuaHttpSsnHookAdd(void *, TSHttpHookID);
-void LuaHttpTxnHookAdd(void *, TSHttpHookID);
+void LuaHttpSsnHookAdd(void *, const LuaPluginInstance *, TSHttpHookID);
+void LuaHttpTxnHookAdd(void *, const LuaPluginInstance *, TSHttpHookID);
 
 // Set a LuaHookReference as the argument on the corresponding object. T can be either TSHttpSsn or TSHttpTxn.
 template <typename T> void LuaSetArgReference(T ptr, int ref);
 
 bool LuaRegisterHttpHooks(lua_State *, void *, LuaHookAddFunction, int);
 
+int LuaDemuxSsnHook(TSHttpHookID hookid, TSCont cont, TSEvent event, void * edata);
+int LuaDemuxTxnHook(TSHttpHookID hookid, TSCont cont, TSEvent event, void * edata);
+int LuaDemuxGlobalHook(TSHttpHookID hookid, TSCont cont, TSEvent event, void * edata);
+
 #endif // LUA_HOOK_H_

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/02414852/plugins/experimental/lua/lutil.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/lua/lutil.cc b/plugins/experimental/lua/lutil.cc
index 0fced5f..43bf027 100644
--- a/plugins/experimental/lua/lutil.cc
+++ b/plugins/experimental/lua/lutil.cc
@@ -23,23 +23,8 @@
 #include <pthread.h>
 #include <unistd.h>
 
-static thread_local_pointer<LuaThreadInstance> LuaThread;
-
-LuaPluginState * LuaPlugin;
 int LuaHttpArgIndex;
 
-LuaThreadInstance::LuaThreadInstance()
-  : lua(NULL)
-{
-  for (unsigned i = 0; i < countof(this->hooks); ++i) {
-    this->hooks[i] = LUA_NOREF;
-  }
-}
-
-LuaThreadInstance::~LuaThreadInstance()
-{
-}
-
 #if !defined(LUAJIT_VERSION)
 static void *
 LuaAllocate(void * ud, void * ptr, size_t osize, size_t nsize)
@@ -56,6 +41,12 @@ LuaAllocate(void * ud, void * ptr, size_t osize, size_t nsize)
 #endif
 
 lua_State *
+LuaNewState()
+{
+  return LuaPluginNewState();
+}
+
+lua_State *
 LuaPluginNewState(void)
 {
   lua_State * lua;
@@ -78,42 +69,6 @@ LuaPluginNewState(void)
   return lua;
 }
 
-lua_State *
-LuaPluginNewState(LuaPluginState * plugin)
-{
-  lua_State * lua;
-
-  lua = LuaPluginNewState();
-  if (lua == NULL) {
-    return NULL;
-  }
-
-  if (!LuaPluginLoad(lua, plugin)) {
-    lua_close(lua);
-    return NULL;
-  }
-
-  return lua;
-}
-
-bool
-LuaPluginLoad(lua_State * lua, LuaPluginState * plugin)
-{
-  for (LuaPluginState::pathlist::const_iterator p = plugin->paths.begin(); p < plugin->paths.end(); ++p) {
-    if (access(p->c_str(), F_OK) != 0) {
-      continue;
-    }
-
-    if (luaL_dofile(lua, p->c_str()) != 0) {
-      // If the load failed, it should have pushed an error message.
-      LuaLogError("failed to load Lua file %s: %s", p->c_str(), lua_tostring(lua, -1));
-      return false;
-    }
-  }
-
-  return true;
-}
-
 void
 LuaPushMetatable(lua_State * lua, const char * name, const luaL_Reg * exports)
 {
@@ -171,15 +126,12 @@ LuaSetConstantField(lua_State * lua, const char * name, const char * value)
   lua_setfield(lua, -2, name);
 }
 
-LuaThreadInstance *
-LuaGetThreadInstance()
-{
-  return LuaThread.get();
-}
-
 void
-LuaSetThreadInstance(LuaThreadInstance * lthread)
+LuaDebugStack(lua_State * lua)
 {
-  LuaThread.set(lthread);
-}
+  int top = lua_gettop(lua);
 
+  for (int i = 1; i <= top; i++) {
+    LuaLogDebug("stack[%d] %s", i, ltypeof(lua, i));
+  }
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/02414852/plugins/experimental/lua/lutil.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/lua/lutil.h b/plugins/experimental/lua/lutil.h
index 0886659..1e2e8e2 100644
--- a/plugins/experimental/lua/lutil.h
+++ b/plugins/experimental/lua/lutil.h
@@ -25,12 +25,6 @@
 #include <memory>
 #include <pthread.h>
 
-
-struct LuaPluginState;
-struct LuaThreadInstance;
-
-extern LuaPluginState * LuaPlugin;
-
 // Global argument index for TSHttpSsnArgGet and TSHttpTxnArgGet.
 extern int LuaHttpArgIndex;
 
@@ -51,6 +45,9 @@ extern int LuaHttpArgIndex;
 #define LuaLogError(fmt, ...) TSError(fmt, ##__VA_ARGS__)
 #endif
 
+// Debug log the Lua stack.
+void LuaDebugStack(lua_State *);
+
 // Return the type name string for the given index.
 static inline const char *
 ltypeof(lua_State * lua, int index) {
@@ -83,65 +80,13 @@ void LuaRegisterLibrary(lua_State * lua, const char * name, lua_CFunction loader
 void LuaSetConstantField(lua_State * lua, const char * name, int value);
 void LuaSetConstantField(lua_State * lua, const char * name, const char * value);
 
-// Get and set the per-thread lua_State.
-LuaThreadInstance * LuaGetThreadInstance();
-void LuaSetThreadInstance(LuaThreadInstance * lua);
-
 // Allocate a new lua_State.
+lua_State * LuaNewState();
 lua_State * LuaPluginNewState(void);
-lua_State * LuaPluginNewState(LuaPluginState * plugin);
-bool LuaPluginLoad(lua_State * lua, LuaPluginState * plugin);
-
-// Global Lua plugin state. Used to reconstruct new lua_States.
-struct LuaPluginState
-{
-  typedef std::vector<std::string> pathlist;
-
-  void init(unsigned argc, const char ** argv) {
-    for (unsigned i = 0; i < argc; ++i) {
-      paths.push_back(argv[i]);
-    }
-  }
-
-  pathlist paths;
-};
-
-// Per-thread lua_State. Used to execute Lua-side code in ethreads.
-struct LuaThreadInstance
-{
-  lua_State * lua;
-  int         hooks[TS_HTTP_LAST_HOOK];
-
-  LuaThreadInstance();
-  ~LuaThreadInstance();
-};
 
 template <typename T, unsigned N> unsigned
 countof(const T (&)[N]) {
   return N;
 }
 
-template <typename T>
-struct thread_local_pointer
-{
-  thread_local_pointer() {
-    TSReleaseAssert(pthread_key_create(&key, NULL) != -1);
-  }
-
-  ~thread_local_pointer() {
-    pthread_key_delete(key);
-  }
-
-  T * get() const {
-    return (T *)pthread_getspecific(key);
-  }
-
-  void set(T * t) const {
-    pthread_setspecific(key, (void *)t);
-  }
-
-private:
-  pthread_key_t key;
-};
-
 #endif // LUA_LUTIL_H_

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/02414852/plugins/experimental/lua/plugin.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/lua/plugin.cc b/plugins/experimental/lua/plugin.cc
index a3e1d7e..65b61a3 100644
--- a/plugins/experimental/lua/plugin.cc
+++ b/plugins/experimental/lua/plugin.cc
@@ -19,12 +19,13 @@
 #include <ts/ts.h>
 #include "lutil.h"
 #include "hook.h"
+#include "state.h"
 
 extern "C" void
 TSPluginInit(int argc, const char * argv[])
 {
-  LuaThreadInstance * lthread;
-  TSPluginRegistrationInfo info;
+  TSPluginRegistrationInfo  info;
+  instanceid_t              instanceid;
 
   info.plugin_name = (char *)"lua";
   info.vendor_name = (char *)"Apache Traffic Server";
@@ -34,21 +35,11 @@ TSPluginInit(int argc, const char * argv[])
       LuaLogError("Plugin registration failed");
   }
 
-  TSAssert(LuaPlugin == NULL);
-
   // Allocate a TSHttpTxn argument index for handling per-transaction hooks.
-  TSReleaseAssert(TSHttpArgIndexReserve(info.plugin_name, info.plugin_name, &LuaHttpArgIndex) == TS_SUCCESS);
-
-  // Create the initial global Lua state.
-  LuaPlugin = tsnew<LuaPluginState>();
-  LuaPlugin->init((unsigned)argc, (const char **)argv);
-
-  // Careful! We need to initialize the per-thread Lua state before we inject
-  // any user code. User code will probably call TSAPI functions, which will
-  // fetch or create the per-thread instance, which had better be available.
-  lthread = tsnew<LuaThreadInstance>();
-  lthread->lua = LuaPluginNewState();
-  LuaSetThreadInstance(lthread);
-  LuaPluginLoad(lthread->lua, LuaPlugin);
+  TSReleaseAssert(TSHttpArgIndexReserve("lua", "lua", &LuaHttpArgIndex) == TS_SUCCESS);
+
+  // Register a new Lua plugin instance, skipping the first argument (which is the plugin name).
+  instanceid = LuaPluginRegister((unsigned)argc - 1, (const char **)argv + 1);
+  TSReleaseAssert(instanceid == 0);
 }
 

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/02414852/plugins/experimental/lua/remap.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/lua/remap.cc b/plugins/experimental/lua/remap.cc
index 21928a4..e168e84 100644
--- a/plugins/experimental/lua/remap.cc
+++ b/plugins/experimental/lua/remap.cc
@@ -18,28 +18,13 @@
 
 #include <ts/ts.h>
 #include <ts/remap.h>
-#include <unistd.h>
-#include <string.h>
 #include "lapi.h"
 #include "lutil.h"
+#include "state.h"
+#include <unistd.h>
+#include <pthread.h>
 
-static TSReturnCode
-LuaPluginRelease(lua_State * lua)
-{
-  lua_getglobal(lua, "release");
-  if (lua_isnil(lua, -1)) {
-    // No "release" callback.
-    return TS_SUCCESS;
-  }
-
-  if (lua_pcall(lua, 0, 0, 0) != 0) {
-    LuaLogDebug("release failed: %s", lua_tostring(lua, -1));
-    lua_pop(lua, 1);
-  }
-
-  lua_close(lua);
-  return TS_SUCCESS;
-}
+static pthread_mutex_t PluginInstanceLock = PTHREAD_MUTEX_INITIALIZER;
 
 static TSRemapStatus
 LuaPluginRemap(lua_State * lua, TSHttpTxn txn, TSRemapRequestInfo * rri)
@@ -68,64 +53,47 @@ LuaPluginRemap(lua_State * lua, TSHttpTxn txn, TSRemapRequestInfo * rri)
   return rq->status;
 }
 
-void
-TSRemapDeleteInstance(void * ih)
-{
-  lua_State * lua = (lua_State *)ih;
-
-  if (lua) {
-    LuaPluginRelease(lua);
-    lua_close(lua);
-  }
-}
-
 TSReturnCode
 TSRemapInit(TSRemapInterface * api_info, char * errbuf, int errbuf_size)
 {
   LuaLogDebug("loading lua plugin");
+
+  // Allocate a TSHttpTxn argument index for handling per-transaction hooks.
+  TSReleaseAssert(TSHttpArgIndexReserve("lua", "lua", &LuaHttpArgIndex) == TS_SUCCESS);
+
   return TS_SUCCESS;
 }
 
 TSReturnCode
 TSRemapNewInstance(int argc, char * argv[], void ** ih, char * errbuf, int errsz)
 {
-  LuaPluginState * remap;
-  lua_State * lua;
-
-  // Copy the plugin arguments so that we can use them to allocate a per-thread Lua state. It would be cleaner
-  // to clone a Lua state, but there's no built-in way to do that, and to implement that ourselves would require
-  // locking the template state (we need to manipulate the stack to copy values out).
-  remap = tsnew<LuaPluginState>();
-  remap->init((unsigned)argc, (const char **)argv);
-
-  // Test whether we can successfully load the Lua program.
-  lua = LuaPluginNewState(remap);
-  if (!lua) {
-    tsdelete(remap);
-    return TS_ERROR;
-  }
+  instanceid_t instanceid;
 
-  *ih = remap;
+  pthread_mutex_lock(&PluginInstanceLock);
+
+  // Register a new Lua plugin instance, skipping the first two arguments (which are the remap URLs).
+  instanceid = LuaPluginRegister((unsigned)argc - 2, (const char **)argv + 2);
+  *ih = (void *)(intptr_t)instanceid;
+
+  pthread_mutex_unlock(&PluginInstanceLock);
   return TS_SUCCESS;
 }
 
-TSRemapStatus
-TSRemapDoRemap(void * ih, TSHttpTxn txn, TSRemapRequestInfo * rri)
+void
+TSRemapDeleteInstance(void * ih)
 {
-  LuaThreadInstance * lthread;
+  instanceid_t instanceid = (intptr_t)ih;
 
-  // Find or clone the per-thread Lua state.
-  lthread = LuaGetThreadInstance();
-  if (!lthread) {
-    LuaPluginState * lps;
-
-    lps = (LuaPluginState *)ih;
-    lthread = tsnew<LuaThreadInstance>();
+  pthread_mutex_lock(&PluginInstanceLock);
+  LuaPluginUnregister(instanceid);
+  pthread_mutex_unlock(&PluginInstanceLock);
+}
 
-    LuaLogDebug("allocating new Lua state on thread 0x%llx", (unsigned long long)pthread_self());
-    lthread->lua = LuaPluginNewState(lps);
-    LuaSetThreadInstance(lthread);
-  }
+TSRemapStatus
+TSRemapDoRemap(void * ih, TSHttpTxn txn, TSRemapRequestInfo * rri)
+{
+  ScopedLuaState lstate((intptr_t)ih);
 
-  return LuaPluginRemap(lthread->lua, txn, rri);
+  TSReleaseAssert(lstate);
+  return LuaPluginRemap(lstate->lua, txn, rri);
 }

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/02414852/plugins/experimental/lua/state.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/lua/state.cc b/plugins/experimental/lua/state.cc
new file mode 100644
index 0000000..37b5f28
--- /dev/null
+++ b/plugins/experimental/lua/state.cc
@@ -0,0 +1,333 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#include <ts/ts.h>
+#include <ts/remap.h>
+#include "state.h"
+#include "hook.h"
+#include "lutil.h"
+#include <unistd.h>
+#include <errno.h>
+
+#define INVALID_INSTANCE_ID (-1)
+
+typedef int (*LuaHookDemuxer)(TSHttpHookID, TSCont, TSEvent, void *);
+
+template <TSHttpHookID hookid, LuaHookDemuxer demuxer> int
+DemuxSpecificHook(TSCont cont, TSEvent event, void * edata) {
+  return demuxer(hookid, cont, event, edata);
+}
+
+template <LuaHookDemuxer demuxer> void
+InitDemuxTable(LuaPluginInstance::demux_table_t& table)
+{
+#define MakeLuaHook(demuxer, hookid) TSContCreate(DemuxSpecificHook<hookid, demuxer>, NULL)
+
+  table[TS_HTTP_READ_REQUEST_HDR_HOOK]  = MakeLuaHook(demuxer, TS_HTTP_READ_REQUEST_HDR_HOOK);
+  table[TS_HTTP_OS_DNS_HOOK]            = MakeLuaHook(demuxer, TS_HTTP_OS_DNS_HOOK);
+  table[TS_HTTP_SEND_REQUEST_HDR_HOOK]  = MakeLuaHook(demuxer, TS_HTTP_SEND_REQUEST_HDR_HOOK);
+  table[TS_HTTP_READ_CACHE_HDR_HOOK]    = MakeLuaHook(demuxer, TS_HTTP_READ_CACHE_HDR_HOOK);
+  table[TS_HTTP_READ_RESPONSE_HDR_HOOK] = MakeLuaHook(demuxer, TS_HTTP_READ_RESPONSE_HDR_HOOK);
+  table[TS_HTTP_SEND_RESPONSE_HDR_HOOK] = MakeLuaHook(demuxer, TS_HTTP_SEND_RESPONSE_HDR_HOOK);
+  table[TS_HTTP_REQUEST_TRANSFORM_HOOK] = MakeLuaHook(demuxer, TS_HTTP_REQUEST_TRANSFORM_HOOK);
+  table[TS_HTTP_RESPONSE_TRANSFORM_HOOK]= MakeLuaHook(demuxer, TS_HTTP_RESPONSE_TRANSFORM_HOOK);
+  table[TS_HTTP_SELECT_ALT_HOOK]        = MakeLuaHook(demuxer, TS_HTTP_SELECT_ALT_HOOK);
+  table[TS_HTTP_TXN_START_HOOK]         = MakeLuaHook(demuxer, TS_HTTP_TXN_START_HOOK);
+  table[TS_HTTP_TXN_CLOSE_HOOK]         = MakeLuaHook(demuxer, TS_HTTP_TXN_CLOSE_HOOK);
+  table[TS_HTTP_SSN_START_HOOK]         = MakeLuaHook(demuxer, TS_HTTP_SSN_START_HOOK);
+  table[TS_HTTP_SSN_CLOSE_HOOK]         = MakeLuaHook(demuxer, TS_HTTP_SSN_CLOSE_HOOK);
+  table[TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK] = MakeLuaHook(demuxer, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK);
+  table[TS_HTTP_PRE_REMAP_HOOK]         = MakeLuaHook(demuxer, TS_HTTP_PRE_REMAP_HOOK);
+  table[TS_HTTP_POST_REMAP_HOOK]        = MakeLuaHook(demuxer, TS_HTTP_POST_REMAP_HOOK);
+}
+
+// Global storage for Lua plugin instances. We vend instanceid_t's as an index into
+// this array.
+static std::vector<LuaPluginInstance *> LuaPluginStorage;
+
+template <typename T> struct is_integral_type {
+  enum { value = 0, is_pointer = 0 };
+};
+
+template <typename T> struct is_integral_type<T *> {
+  enum { value = 1, is_pointer = 1 };
+};
+
+static unsigned
+nproc()
+{
+  long count;
+
+  count = sysconf(_SC_NPROCESSORS_ONLN);
+  return (unsigned)std::max(count, 1l);
+}
+
+static unsigned
+thread_id()
+{
+  pthread_t self = pthread_self();
+
+  if (is_integral_type<pthread_t>::value) {
+    // If it's a pointer, then the lower bits are probably zero because it's
+    // likely to be 8 or 16 byte aligned.
+    if (is_integral_type<pthread_t>::is_pointer) {
+      return (unsigned)((intptr_t)self >> 4);
+    }
+    return (unsigned)(intptr_t)self;
+  } else {
+    // XXX make this work on FreeBSD!
+    TSReleaseAssert(0 && "unsupported platform");
+    return 0;
+  }
+}
+
+LuaPluginInstance::LuaPluginInstance()
+  : instanceid(INVALID_INSTANCE_ID), paths(), states()
+{
+}
+
+LuaPluginInstance::~LuaPluginInstance()
+{
+  this->invalidate();
+}
+
+void
+LuaPluginInstance::invalidate()
+{
+  for (unsigned i = 0; i < this->states.size(); ++ i) {
+    tsdelete(this->states[i]);
+  }
+
+  this->states.clear();
+  this->paths.clear();
+  this->instanceid = INVALID_INSTANCE_ID;
+
+  for (unsigned i = 0; i < countof(this->demux.global); ++i) {
+    TSContDestroy(this->demux.global[i]);
+    TSContDestroy(this->demux.ssn[i]);
+    TSContDestroy(this->demux.txn[i]);
+    this->demux.global[i] = this->demux.ssn[i] = this->demux.txn[i] = NULL;
+  }
+
+}
+
+void
+LuaPluginInstance::init(unsigned argc, const char ** argv)
+{
+  for (unsigned i = 0; i < argc; ++i) {
+    this->paths.push_back(argv[i]);
+  }
+
+  // Make sure we have enough threads to make concurrent access to lua
+  // states unlikely.
+  this->states.resize(nproc() * 2);
+
+  InitDemuxTable<LuaDemuxGlobalHook>(this->demux.global);
+  InitDemuxTable<LuaDemuxSsnHook>(this->demux.ssn);
+  InitDemuxTable<LuaDemuxTxnHook>(this->demux.txn);
+
+  for (unsigned i = 0; i < countof(this->demux.global); ++i) {
+    TSContDataSet(this->demux.global[i], (void *)(uintptr_t)this->instanceid);
+    TSContDataSet(this->demux.ssn[i], (void *)(uintptr_t)this->instanceid);
+    TSContDataSet(this->demux.txn[i], (void *)(uintptr_t)this->instanceid);
+  }
+
+}
+
+instanceid_t
+LuaPluginRegister(unsigned argc, const char ** argv)
+{
+  instanceid_t instanceid = INVALID_INSTANCE_ID;
+  LuaPluginInstance * plugin;
+
+  LuaLogDebug("registering plugin");
+  // OK, first we try to find an unused instance slot.
+  for (unsigned i = 0; i < LuaPluginStorage.size(); ++i) {
+    if (LuaPluginStorage[i]) {
+      // This slot looks ok, let's try to claim it.
+      instanceid = i;
+      break;
+    }
+  }
+
+  // Take the current instanceid, incrementing it for next time.
+  if (instanceid == INVALID_INSTANCE_ID) {
+    instanceid = LuaPluginStorage.size();
+    LuaPluginStorage.resize(LuaPluginStorage.size() + 1);
+  }
+
+  // Mark this plugin instance as in use.
+  plugin = LuaPluginStorage[instanceid] = tsnew<LuaPluginInstance>();
+  plugin->instanceid = instanceid;
+
+  // The path list should be empty if we correctly released it last time this
+  // instance ID was used.
+  TSReleaseAssert(plugin->paths.empty());
+  LuaPluginStorage[instanceid]->init(argc, argv);
+
+  // Allocate the Lua states, then separately initialize by evaluating all the Lua files.
+  for (unsigned i = 0; i < plugin->states.size(); ++i) {
+    plugin->states[i] = tsnew<LuaThreadState>();
+    plugin->states[i]->alloc(plugin, i);
+  }
+
+  for (unsigned i = 0; i < LuaPluginStorage[instanceid]->states.size(); ++i) {
+    plugin->states[i]->init(plugin);
+  }
+
+  return instanceid;
+}
+
+void
+LuaPluginUnregister(instanceid_t instanceid)
+{
+  TSReleaseAssert(instanceid < LuaPluginStorage.size());
+  tsdelete(LuaPluginStorage[instanceid]);
+  LuaPluginStorage[instanceid] = NULL;
+}
+
+LuaThreadState::LuaThreadState()
+  : lua(NULL), instance(NULL)
+{
+  pthread_mutexattr_t attr;
+
+  // We need a recursive mutex so that we can safely reacquire it from Lua code.
+  TSReleaseAssert(pthread_mutexattr_init(&attr) == 0);
+  TSReleaseAssert(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) == 0);
+  TSReleaseAssert(pthread_mutex_init(&this->mutex, &attr) == 0);
+
+  for (unsigned i = 0; i < countof(this->hookrefs); ++i) {
+    this->hookrefs[i] = LUA_NOREF;
+  }
+
+  pthread_mutexattr_destroy(&attr);
+}
+
+LuaThreadState::~LuaThreadState()
+{
+  this->release();
+  pthread_mutex_destroy(&this->mutex);
+}
+
+bool
+LuaThreadState::alloc(LuaPluginInstance * plugin, unsigned threadid)
+{
+  this->lua = LuaNewState();
+  this->instance = plugin;
+
+  // Push the instanceid into a global integer. We will use this later to rendevous
+  // with the lthread from the lua_State. We have to set the instanceid global before
+  // executing any Lua code, because that will almost certainly call back into the plugin
+  // ad reguire the instance id to be set.
+  lua_pushinteger(this->lua, plugin->instanceid);
+  lua_setfield(this->lua, LUA_REGISTRYINDEX, "__instanceid");
+
+  lua_pushinteger(this->lua, threadid);
+  lua_setfield(this->lua, LUA_REGISTRYINDEX, "__threadid");
+
+  return true;
+}
+
+bool
+LuaThreadState::init(LuaPluginInstance * plugin)
+{
+  for (LuaPluginInstance::pathlist_t::const_iterator p = plugin->paths.begin(); p < plugin->paths.end(); ++p) {
+    if (access(p->c_str(), F_OK) != 0) {
+      LuaLogError("%s: %s", p->c_str(), strerror(errno));
+      continue;
+    }
+
+    if (luaL_dofile(this->lua, p->c_str()) != 0) {
+      // If the load failed, it should have pushed an error message.
+      LuaLogError("failed to load Lua file %s: %s", p->c_str(), lua_tostring(lua, -1));
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void
+LuaThreadState::release()
+{
+  if (this->lua) {
+    lua_close(this->lua);
+    this->lua = NULL;
+  }
+}
+
+std::pair<LuaThreadState *, LuaPluginInstance *>
+LuaThreadStateAcquire(lua_State * lua)
+{
+  LuaThreadState * lthread;
+  LuaPluginInstance * instance;
+  instanceid_t instanceid;
+  unsigned threadid;
+
+  lua_getfield(lua, LUA_REGISTRYINDEX, "__instanceid");
+  instanceid = (instanceid_t)luaL_checkinteger(lua, -1);
+
+  lua_getfield(lua, LUA_REGISTRYINDEX, "__threadid");
+  threadid = (unsigned)luaL_checkinteger(lua, -1);
+
+  TSReleaseAssert(instanceid < LuaPluginStorage.size());
+
+  instance = LuaPluginStorage[instanceid];
+
+  TSReleaseAssert(threadid < instance->states.size());
+  lthread = instance->states[threadid];
+
+  LuaLogDebug("%u/%p acquired state %u from plugin instance %u on thread %u",
+      instanceid, lthread->lua, threadid, instanceid, thread_id());
+
+  lua_pop(lua, 2);
+
+  // Since we already hav a lua_State, we must already be holding the lock. But acquire
+  // and release come in matched pairs, so we need a recursive lock to release.
+  TSReleaseAssert(pthread_mutex_lock(&lthread->mutex) == 0);
+  return std::make_pair(lthread, instance);
+}
+
+std::pair<LuaThreadState *, LuaPluginInstance *>
+LuaThreadStateAcquire(instanceid_t instanceid)
+{
+  LuaThreadState * lthread;
+  LuaPluginInstance * instance;
+  unsigned which;
+
+  TSReleaseAssert(instanceid < LuaPluginStorage.size());
+
+  instance = LuaPluginStorage[instanceid];
+
+  // Index the set of LuaThreadStates with the thread ID. We might want to do a proper
+  // hash on this to prevent false sharing.
+  which = thread_id() % instance->states.size();
+  lthread = instance->states[which];
+
+  LuaLogDebug("%u/%p acquired state %u from plugin instance %u on thread %u",
+      instanceid, lthread->lua, which, instanceid, thread_id());
+
+  TSReleaseAssert(pthread_mutex_lock(&lthread->mutex) == 0);
+  return std::make_pair(lthread, instance);
+}
+
+void
+LuaThreadStateRelease(LuaThreadState * lthread)
+{
+  TSReleaseAssert(pthread_mutex_unlock(&lthread->mutex) == 0);
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/02414852/plugins/experimental/lua/state.h
----------------------------------------------------------------------
diff --git a/plugins/experimental/lua/state.h b/plugins/experimental/lua/state.h
new file mode 100644
index 0000000..3cdf810
--- /dev/null
+++ b/plugins/experimental/lua/state.h
@@ -0,0 +1,157 @@
+/*
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+#ifndef LUA_STATE_H_
+#define LUA_STATE_H_
+
+#include <string>
+#include <vector>
+#include <utility>
+#include <pthread.h>
+#include <lua.hpp>
+
+/*
+
+Lua Plugin threading model
+
+For remapping, we need to support multiple indepedent Lua plugin
+instances. Each instance is handled by a LuaPluginInstance object.
+Each plugin instance maintains a pool of lua_States which are
+independent Lua interpeters. The LuaThreadState object owns a single
+lua_State, holding additional hook data that is needed to de-multiplex
+events.
+
+There are two basic code paths to obtaining a LuaThreadState. If
+we already have a lua_State, then we can use the __instanceid and
+__threadid global variables to identify the LuaThreadState object.
+If we don't have a lua_State, then we know the instance ID from the
+hook continuation data (attached per LuaPluginInstance), and we
+choose a state by hashing the thread ID.
+
+  Traffic Server +-> LuaPluginInstance[0]
+                 |   +-> LuaThreadState[0]
+                 |   +-> LuaThreadState[1]
+                 |   +-> LuaThreadState[2]
+                 |   +-> LuaThreadState[3]
+                 |
+                 +-> LuaPluginInstance[1]
+                 |   +-> LuaThreadState[0]
+                 |   +-> LuaThreadState[1]
+                 |   +-> LuaThreadState[2]
+                 |   +-> LuaThreadState[3]
+                 |
+                 +-> LuaPluginInstance[2]
+                     +-> LuaThreadState[0]
+                     +-> LuaThreadState[1]
+                     +-> LuaThreadState[2]
+                     +-> LuaThreadState[3]
+
+*/
+
+typedef uint32_t instanceid_t;
+
+struct LuaThreadState;
+struct LuaPluginInstance;
+
+// Per-thread lua_State. Used to execute Lua-side code in ethreads.
+struct LuaThreadState
+{
+  lua_State *         lua;
+  int                 hookrefs[TS_HTTP_LAST_HOOK];
+  LuaPluginInstance * instance;
+
+  pthread_mutex_t mutex;
+
+  LuaThreadState();
+  ~LuaThreadState();
+
+  bool alloc(LuaPluginInstance *, unsigned);
+  bool init(LuaPluginInstance *);
+  void release();
+
+private:
+  LuaThreadState(const LuaThreadState&); // disable
+  LuaThreadState& operator=(const LuaThreadState&); // disable
+};
+
+struct LuaPluginInstance
+{
+  typedef std::vector<std::string> pathlist_t;
+  typedef TSCont demux_table_t[TS_HTTP_LAST_HOOK];
+
+  LuaPluginInstance();
+  ~LuaPluginInstance();
+
+  void invalidate();
+  void init(unsigned argc, const char ** argv);
+
+  struct {
+    demux_table_t global;
+    demux_table_t txn;
+    demux_table_t ssn;
+  } demux;
+
+  instanceid_t  instanceid;
+  pathlist_t    paths;
+  std::vector<LuaThreadState *> states;
+
+private:
+  LuaPluginInstance(const LuaPluginInstance&); // disable
+  LuaPluginInstance& operator=(const LuaPluginInstance&); // disable
+};
+
+instanceid_t LuaPluginRegister(unsigned argc, const char ** argv);
+void LuaPluginUnregister(instanceid_t instanceid);
+
+// Acquire a locked Lua thread state belonging to the given instance.
+std::pair<LuaThreadState *, LuaPluginInstance *> LuaThreadStateAcquire(instanceid_t);
+std::pair<LuaThreadState *, LuaPluginInstance *> LuaThreadStateAcquire(lua_State *);
+// Return the previously acquired Lua thread state.
+void LuaThreadStateRelease(LuaThreadState *);
+
+struct ScopedLuaState
+{
+  explicit ScopedLuaState(instanceid_t instanceid)
+    : ptr(LuaThreadStateAcquire(instanceid)) {
+  }
+
+  explicit ScopedLuaState(lua_State * lua)
+    : ptr(LuaThreadStateAcquire(lua)) {
+  }
+
+  ~ScopedLuaState() {
+    LuaThreadStateRelease(this->ptr.first);
+  }
+
+  operator bool() const {
+    return this->ptr.first != NULL;
+  }
+
+  LuaThreadState * operator->() const {
+    return this->ptr.first;
+  }
+
+  LuaPluginInstance * instance() const {
+    return this->ptr.second;
+  }
+
+private:
+  std::pair<LuaThreadState *, LuaPluginInstance *> ptr;
+};
+
+#endif // LUA_STATE_H_


Mime
View raw message