mirror of
https://github.com/vxcontrol/lualibs-lanes.git
synced 2026-07-01 10:03:33 -04:00
upgrade to 3.9.6 & osx builds
This commit is contained in:
+5
-3
@@ -1,3 +1,5 @@
|
||||
LuaLanes 3.7.0 from https://github.com/LuaLanes/lanes (MIT license)
|
||||
|
||||
lanes.lua moved to root dir.
|
||||
LuaLanes 3.9.3 from https://github.com/LuaLanes/lanes (MIT license)
|
||||
|
||||
lanes.lua moved to root dir.
|
||||
|
||||
PROPAGATE_ALLOCF set to 0 in tools.h (needed for 64bit LuaJIT builds)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
mkdir -p ../../bin/linux32/clib/lanes
|
||||
gcc -O2 -s -static-libgcc *.c -shared -o ../../bin/linux32/clib/lanes/core.so -I. -I../lua -pthread -DNDEBUG
|
||||
gcc -O2 -s -static-libgcc src/*.c -shared -o ../../bin/linux32/clib/lanes/core.so -I. -I../lua -pthread -DNDEBUG
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
mkdir -p ../../bin/linux64/clib/lanes
|
||||
gcc -O2 -s -static-libgcc -fPIC *.c -shared -o ../../bin/linux64/clib/lanes/core.so -I. -I../lua -pthread -DNDEBUG
|
||||
gcc -O2 -s -static-libgcc -fPIC src/*.c -shared -o ../../bin/linux64/clib/lanes/core.so -I. -I../lua -pthread -DNDEBUG
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# NOTE: lanes thinks WinXP is 0x400 not 0x500
|
||||
mkdir -p ../../bin/mingw32/clib/lanes
|
||||
gcc -O2 -s -static-libgcc *.c -shared -o ../../bin/mingw32/clib/lanes/core.dll -I. -I../lua -L../../bin/mingw32 -llua51 -DNDEBUG -DWINVER=0x0400
|
||||
gcc -O2 -s -static-libgcc src/*.c -shared -o ../../bin/mingw32/clib/lanes/core.dll -I. -I../lua -L../../bin/mingw32 -llua51 -DNDEBUG -DWINVER=0x0400
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# NOTE: lanes thinks WinXP is 0x400 not 0x500
|
||||
mkdir -p ../../bin/mingw64/clib/lanes
|
||||
gcc -O2 -s -static-libgcc *.c -shared -o ../../bin/mingw64/clib/lanes/core.dll -I. -I../lua -L../../bin/mingw64 -llua51 -DNDEBUG -DWINVER=0x0400
|
||||
gcc -O2 -s -static-libgcc src/*.c -shared -o ../../bin/mingw64/clib/lanes/core.dll -I. -I../lua -L../../bin/mingw64 -llua51 -DNDEBUG -DWINVER=0x0400
|
||||
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
mkdir -p ../../bin/osx32/clib/lanes
|
||||
gcc -arch i386 -O2 src/*.c -shared -o ../../bin/osx32/clib/lanes/core.so -I. -I../lua -undefined dynamic_lookup -DNDEBUG
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
mkdir -p ../../bin/osx64/clib/lanes
|
||||
gcc -arch x86_64 -O2 src/*.c -shared -o ../../bin/osx64/clib/lanes/core.so -I. -I../lua -undefined dynamic_lookup -DNDEBUG
|
||||
@@ -1,41 +0,0 @@
|
||||
#if !defined( __keeper_h__)
|
||||
#define __keeper_h__ 1
|
||||
|
||||
struct s_Keeper
|
||||
{
|
||||
MUTEX_T lock_;
|
||||
lua_State *L;
|
||||
//int count;
|
||||
};
|
||||
|
||||
// if enabled, call close_keepers at the very last as we want to be sure no thread is GCing after.
|
||||
// (and therefore may perform linda object dereferencing after keepers are gone)
|
||||
// problem: maybe on some platforms (linux) atexit() is called after DLL/so are unloaded...
|
||||
#define HAVE_KEEPER_ATEXIT_DESINIT 0
|
||||
|
||||
char const* init_keepers( lua_State* L);
|
||||
#if !HAVE_KEEPER_ATEXIT_DESINIT
|
||||
void close_keepers( void);
|
||||
#endif // HAVE_KEEPER_ATEXIT_DESINIT
|
||||
|
||||
struct s_Keeper *keeper_acquire( const void *ptr);
|
||||
void keeper_release( struct s_Keeper *K);
|
||||
void keeper_toggle_nil_sentinels( lua_State *L, int _val_i, int _nil_to_sentinel);
|
||||
int keeper_push_linda_storage( lua_State* L, void* ptr);
|
||||
|
||||
typedef lua_CFunction keeper_api_t;
|
||||
#define KEEPER_API( _op) keepercall_ ## _op
|
||||
#define PUSH_KEEPER_FUNC lua_pushcfunction
|
||||
// lua_Cfunctions to run inside a keeper state (formerly implemented in Lua)
|
||||
int keepercall_clear( lua_State* L);
|
||||
int keepercall_send( lua_State* L);
|
||||
int keepercall_receive( lua_State* L);
|
||||
int keepercall_receive_batched( lua_State* L);
|
||||
int keepercall_limit( lua_State* L);
|
||||
int keepercall_get( lua_State* L);
|
||||
int keepercall_set( lua_State* L);
|
||||
int keepercall_count( lua_State* L);
|
||||
|
||||
int keeper_call( lua_State *K, keeper_api_t _func, lua_State *L, void *linda, uint_t starting_index);
|
||||
|
||||
#endif // __keeper_h__
|
||||
@@ -15,9 +15,17 @@
|
||||
#define LANES_API
|
||||
#endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)
|
||||
|
||||
typedef void (*luaG_IdFunction)( lua_State* L, char const* const which);
|
||||
enum eDeepOp
|
||||
{
|
||||
eDO_new,
|
||||
eDO_delete,
|
||||
eDO_metatable,
|
||||
eDO_module,
|
||||
};
|
||||
|
||||
extern LANES_API int luaG_deep_userdata( lua_State* L, luaG_IdFunction idfunc);
|
||||
typedef void* (*luaG_IdFunction)( lua_State* L, enum eDeepOp op_);
|
||||
|
||||
extern LANES_API int luaG_newdeepuserdata( lua_State* L, luaG_IdFunction idfunc);
|
||||
extern LANES_API void* luaG_todeep( lua_State* L, luaG_IdFunction idfunc, int index);
|
||||
|
||||
#endif // __LANES_DEEP_H__
|
||||
@@ -44,6 +44,7 @@
|
||||
#include <ctype.h>
|
||||
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
#include "lauxlib.h"
|
||||
|
||||
#include "threading.h"
|
||||
@@ -95,7 +96,7 @@ static void fifo_new( lua_State* L)
|
||||
}
|
||||
|
||||
// in: expect fifo ... on top of the stack
|
||||
// out: nothing, removes all pushed values on the stack
|
||||
// out: nothing, removes all pushed values from the stack
|
||||
static void fifo_push( lua_State* L, keeper_fifo* fifo, int _count)
|
||||
{
|
||||
int idx = lua_gettop( L) - _count;
|
||||
@@ -115,11 +116,11 @@ static void fifo_push( lua_State* L, keeper_fifo* fifo, int _count)
|
||||
// expects exactly 1 value on the stack!
|
||||
// currently only called with a count of 1, but this may change in the future
|
||||
// function assumes that there is enough data in the fifo to satisfy the request
|
||||
static void fifo_peek( lua_State* L, keeper_fifo* fifo, int _count)
|
||||
static void fifo_peek( lua_State* L, keeper_fifo* fifo, int count_)
|
||||
{
|
||||
int i;
|
||||
STACK_GROW( L, _count);
|
||||
for( i = 0; i < _count; ++ i)
|
||||
STACK_GROW( L, count_);
|
||||
for( i = 0; i < count_; ++ i)
|
||||
{
|
||||
lua_rawgeti( L, 1, fifo->first + i);
|
||||
}
|
||||
@@ -127,14 +128,14 @@ static void fifo_peek( lua_State* L, keeper_fifo* fifo, int _count)
|
||||
|
||||
// in: fifo
|
||||
// out: remove the fifo from the stack, push as many items as required on the stack (function assumes they exist in sufficient number)
|
||||
static void fifo_pop( lua_State* L, keeper_fifo* fifo, int _count)
|
||||
static void fifo_pop( lua_State* L, keeper_fifo* fifo, int count_)
|
||||
{
|
||||
int fifo_idx = lua_gettop( L); // ... fifo
|
||||
int i;
|
||||
// each iteration pushes a value on the stack!
|
||||
STACK_GROW( L, _count + 2);
|
||||
STACK_GROW( L, count_ + 2);
|
||||
// skip first item, we will push it last
|
||||
for( i = 1; i < _count; ++ i)
|
||||
for( i = 1; i < count_; ++ i)
|
||||
{
|
||||
int const at = fifo->first + i;
|
||||
// push item on the stack
|
||||
@@ -151,8 +152,12 @@ static void fifo_pop( lua_State* L, keeper_fifo* fifo, int _count)
|
||||
lua_rawseti( L, fifo_idx, at); // ... fifo vals val
|
||||
lua_replace( L, fifo_idx); // ... vals
|
||||
}
|
||||
fifo->first += _count;
|
||||
fifo->count -= _count;
|
||||
{
|
||||
// avoid ever-growing indexes by resetting each time we detect the fifo is empty
|
||||
int const new_count = fifo->count - count_;
|
||||
fifo->first = (new_count == 0) ? 1 : (fifo->first + count_);
|
||||
fifo->count = new_count;
|
||||
}
|
||||
}
|
||||
|
||||
// in: linda_ud expected at *absolute* stack slot idx
|
||||
@@ -181,11 +186,12 @@ static void push_table( lua_State* L, int idx)
|
||||
STACK_END( L, 1);
|
||||
}
|
||||
|
||||
int keeper_push_linda_storage( lua_State* L, void* ptr)
|
||||
int keeper_push_linda_storage( struct s_Universe* U, lua_State* L, void* ptr, unsigned long magic_)
|
||||
{
|
||||
struct s_Keeper* K = keeper_acquire( ptr);
|
||||
struct s_Keeper* K = keeper_acquire( U->keepers, magic_);
|
||||
lua_State* KL = K ? K->L : NULL;
|
||||
if( KL == NULL) return 0;
|
||||
STACK_GROW( KL, 4);
|
||||
STACK_CHECK( KL);
|
||||
lua_pushlightuserdata( KL, fifos_key); // fifos_key
|
||||
lua_rawget( KL, LUA_REGISTRYINDEX); // fifos
|
||||
@@ -200,16 +206,17 @@ int keeper_push_linda_storage( lua_State* L, void* ptr)
|
||||
}
|
||||
// move data from keeper to destination state KEEPER MAIN
|
||||
lua_pushnil( KL); // storage nil
|
||||
STACK_GROW( L, 5);
|
||||
STACK_CHECK( L);
|
||||
lua_newtable( L); // out
|
||||
while( lua_next( KL, -2)) // storage key fifo
|
||||
{
|
||||
keeper_fifo* fifo = prepare_fifo_access( KL, -1); // storage key fifo
|
||||
lua_pushvalue( KL, -2); // storage key fifo key
|
||||
luaG_inter_move( KL, L, 1, eLM_FromKeeper); // storage key fifo // out key
|
||||
luaG_inter_move( U, KL, L, 1, eLM_FromKeeper); // storage key fifo // out key
|
||||
STACK_MID( L, 2);
|
||||
lua_newtable( L); // out key keyout
|
||||
luaG_inter_move( KL, L, 1, eLM_FromKeeper); // storage key // out key keyout fifo
|
||||
luaG_inter_move( U, KL, L, 1, eLM_FromKeeper); // storage key // out key keyout fifo
|
||||
lua_pushinteger( L, fifo->first); // out key keyout fifo first
|
||||
STACK_MID( L, 5);
|
||||
lua_setfield( L, -3, "first"); // out key keyout fifo
|
||||
@@ -349,7 +356,7 @@ int keepercall_receive_batched( lua_State* L)
|
||||
}
|
||||
|
||||
// in: linda_ud key n
|
||||
// out: nothing
|
||||
// out: true or nil
|
||||
int keepercall_limit( lua_State* L)
|
||||
{
|
||||
keeper_fifo* fifo;
|
||||
@@ -358,81 +365,116 @@ int keepercall_limit( lua_State* L)
|
||||
lua_replace( L, 1); // fifos key n
|
||||
lua_pop( L, 1); // fifos key
|
||||
lua_pushvalue( L, -1); // fifos key key
|
||||
lua_rawget( L, -3); // fifos key fifo
|
||||
lua_rawget( L, -3); // fifos key fifo|nil
|
||||
fifo = (keeper_fifo*) lua_touserdata( L, -1);
|
||||
if( fifo == NULL)
|
||||
{
|
||||
{ // fifos key nil
|
||||
lua_pop( L, 1); // fifos key
|
||||
fifo_new( L); // fifos key fifo
|
||||
fifo = (keeper_fifo*) lua_touserdata( L, -1);
|
||||
lua_rawset( L, -3); // fifos
|
||||
}
|
||||
// remove any clutter on the stack
|
||||
lua_settop( L, 0);
|
||||
// return true if we decide that blocked threads waiting to write on that key should be awakened
|
||||
// this is the case if we detect the key was full but it is no longer the case
|
||||
if(
|
||||
((fifo->limit >= 0) && (fifo->count >= fifo->limit)) // the key was full if limited and count exceeded the previous limit
|
||||
&& ((limit < 0) || (fifo->count < limit)) // the key is not full if unlimited or count is lower than the new limit
|
||||
)
|
||||
{
|
||||
lua_pushboolean( L, 1);
|
||||
}
|
||||
// set the new limit
|
||||
fifo->limit = limit;
|
||||
return 0;
|
||||
// return 0 or 1 value
|
||||
return lua_gettop( L);
|
||||
}
|
||||
|
||||
//in: linda_ud key [val]
|
||||
//in: linda_ud key [[val] ...]
|
||||
//out: true or nil
|
||||
int keepercall_set( lua_State* L)
|
||||
{
|
||||
bool_t should_wake_writers = FALSE;
|
||||
STACK_GROW( L, 6);
|
||||
// make sure we have a value on the stack
|
||||
if( lua_gettop( L) == 2) // ud key val?
|
||||
{
|
||||
lua_pushnil( L); // ud key nil
|
||||
}
|
||||
|
||||
// retrieve fifos associated with the linda
|
||||
push_table( L, 1); // ud key val fifos
|
||||
lua_replace( L, 1); // fifos key val
|
||||
push_table( L, 1); // ud key [val [, ...]] fifos
|
||||
lua_replace( L, 1); // fifos key [val [, ...]]
|
||||
|
||||
if( !lua_isnil( L, 3)) // set/replace contents stored at the specified key?
|
||||
// make sure we have a value on the stack
|
||||
if( lua_gettop( L) == 2) // fifos key
|
||||
{
|
||||
keeper_fifo* fifo;
|
||||
lua_pushvalue( L, -2); // fifos key val key
|
||||
lua_rawget( L, 1); // fifos key val fifo|nil
|
||||
lua_pushvalue( L, -1); // fifos key key
|
||||
lua_rawget( L, 1); // fifos key fifo|nil
|
||||
// empty the fifo for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged!
|
||||
fifo = (keeper_fifo*) lua_touserdata( L, -1);
|
||||
if( fifo == NULL) // might be NULL if we set a nonexistent key to nil
|
||||
{
|
||||
lua_pop( L, 1); // fifos key val
|
||||
fifo_new( L); // fifos key val fifo
|
||||
lua_pushvalue( L, 2); // fifos key val fifo key
|
||||
lua_pushvalue( L, -2); // fifos key val fifo key fifo
|
||||
lua_rawset( L, 1); // fifos key val fifo
|
||||
if( fifo != NULL) // might be NULL if we set a nonexistent key to nil
|
||||
{ // fifos key fifo
|
||||
if( fifo->limit < 0) // fifo limit value is the default (unlimited): we can totally remove it
|
||||
{
|
||||
lua_pop( L, 1); // fifos key
|
||||
lua_pushnil( L); // fifos key nil
|
||||
lua_rawset( L, -3); // fifos
|
||||
}
|
||||
else
|
||||
{
|
||||
// we create room if the fifo was full but it is no longer the case
|
||||
should_wake_writers = (fifo->limit > 0) && (fifo->count >= fifo->limit);
|
||||
lua_remove( L, -2); // fifos fifo
|
||||
lua_newtable( L); // fifos fifo {}
|
||||
lua_setuservalue( L, -2); // fifos fifo
|
||||
fifo->first = 1;
|
||||
fifo->count = 0;
|
||||
}
|
||||
}
|
||||
else // the fifo exists, we just want to clear its contents
|
||||
{
|
||||
}
|
||||
else // set/replace contents stored at the specified key?
|
||||
{
|
||||
int count = lua_gettop( L) - 2; // number of items we want to store
|
||||
keeper_fifo* fifo; // fifos key [val [, ...]]
|
||||
lua_pushvalue( L, 2); // fifos key [val [, ...]] key
|
||||
lua_rawget( L, 1); // fifos key [val [, ...]] fifo|nil
|
||||
fifo = (keeper_fifo*) lua_touserdata( L, -1);
|
||||
if( fifo == NULL) // can be NULL if we store a value at a new key
|
||||
{ // fifos key [val [, ...]] nil
|
||||
// no need to wake writers in that case, because a writer can't wait on an inexistent key
|
||||
lua_pop( L, 1); // fifos key [val [, ...]]
|
||||
fifo_new( L); // fifos key [val [, ...]] fifo
|
||||
lua_pushvalue( L, 2); // fifos key [val [, ...]] fifo key
|
||||
lua_pushvalue( L, -2); // fifos key [val [, ...]] fifo key fifo
|
||||
lua_rawset( L, 1); // fifos key [val [, ...]] fifo
|
||||
}
|
||||
else // the fifo exists, we just want to update its contents
|
||||
{ // fifos key [val [, ...]] fifo
|
||||
// we create room if the fifo was full but it is no longer the case
|
||||
should_wake_writers = (fifo->limit > 0) && (fifo->count >= fifo->limit) && (count < fifo->limit);
|
||||
// empty the fifo for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged!
|
||||
lua_newtable( L); // fifos key val fifo {}
|
||||
lua_setuservalue( L, -2); // fifos key val fifo
|
||||
lua_newtable( L); // fifos key [val [, ...]] fifo {}
|
||||
lua_setuservalue( L, -2); // fifos key [val [, ...]] fifo
|
||||
fifo->first = 1;
|
||||
fifo->count = 0;
|
||||
}
|
||||
fifo = prepare_fifo_access( L, -1);
|
||||
lua_insert( L, -2); // fifos key fifo val
|
||||
fifo_push( L, fifo, 1); // fifos key fifo
|
||||
// move the fifo below the values we want to store
|
||||
lua_insert( L, 3); // fifos key fifo [val [, ...]]
|
||||
fifo_push( L, fifo, count); // fifos key fifo
|
||||
}
|
||||
else // val == nil // fifos key nil
|
||||
{
|
||||
keeper_fifo* fifo;
|
||||
lua_pop( L, 1); // fifos key
|
||||
lua_rawget( L, 1); // fifos fifo|nil
|
||||
// empty the fifo for the specified key: replace uservalue with a virgin table, reset counters, but leave limit unchanged!
|
||||
fifo = (keeper_fifo*) lua_touserdata( L, -1);
|
||||
if( fifo != NULL) // might be NULL if we set a nonexistent key to nil
|
||||
{
|
||||
lua_newtable( L); // fifos fifo {}
|
||||
lua_setuservalue( L, -2); // fifos fifo
|
||||
fifo->first = 1;
|
||||
fifo->count = 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
return should_wake_writers ? (lua_pushboolean( L, 1), 1) : 0;
|
||||
}
|
||||
|
||||
// in: linda_ud key
|
||||
// in: linda_ud key [count]
|
||||
// out: at most <count> values
|
||||
int keepercall_get( lua_State* L)
|
||||
{
|
||||
keeper_fifo* fifo;
|
||||
int count = 1;
|
||||
if( lua_gettop( L) == 3) // ud key count
|
||||
{
|
||||
count = lua_tointeger( L, 3);
|
||||
lua_pop( L, 1); // ud key
|
||||
}
|
||||
push_table( L, 1); // ud key fifos
|
||||
lua_replace( L, 1); // fifos key
|
||||
lua_rawget( L, 1); // fifos fifo
|
||||
@@ -440,9 +482,10 @@ int keepercall_get( lua_State* L)
|
||||
if( fifo != NULL && fifo->count > 0)
|
||||
{
|
||||
lua_remove( L, 1); // fifo
|
||||
// read one value off the fifo
|
||||
fifo_peek( L, fifo, 1); // fifo ...
|
||||
return 1;
|
||||
count = __min( count, fifo->count);
|
||||
// read <count> value off the fifo
|
||||
fifo_peek( L, fifo, count); // fifo ...
|
||||
return count;
|
||||
}
|
||||
// no fifo was ever registered for this key, or it is empty
|
||||
return 0;
|
||||
@@ -476,11 +519,18 @@ int keepercall_count( lua_State* L)
|
||||
{
|
||||
keeper_fifo* fifo;
|
||||
lua_replace( L, 1); // fifos key
|
||||
lua_rawget( L, -2); // fifos fifo
|
||||
fifo = prepare_fifo_access( L, -1); // fifos fifo
|
||||
lua_pushinteger( L, fifo->count); // fifos fifo count
|
||||
lua_replace( L, -3); // count fifo
|
||||
lua_pop( L, 1); // count
|
||||
lua_rawget( L, -2); // fifos fifo|nil
|
||||
if( lua_isnil( L, -1)) // the key is unknown
|
||||
{ // fifos nil
|
||||
lua_remove( L, -2); // nil
|
||||
}
|
||||
else // the key is known
|
||||
{ // fifos fifo
|
||||
fifo = prepare_fifo_access( L, -1); // fifos fifo
|
||||
lua_pushinteger( L, fifo->count); // fifos fifo count
|
||||
lua_replace( L, -3); // count fifo
|
||||
lua_pop( L, 1); // count
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -494,21 +544,22 @@ int keepercall_count( lua_State* L)
|
||||
{
|
||||
keeper_fifo* fifo;
|
||||
lua_pushvalue( L, -1); // out fifos keys key
|
||||
lua_rawget( L, 2); // out fifos keys fifo
|
||||
fifo = prepare_fifo_access( L, -1); // out fifos keys fifo
|
||||
lua_rawget( L, 2); // out fifos keys fifo|nil
|
||||
fifo = prepare_fifo_access( L, -1); // out fifos keys fifo|nil
|
||||
lua_pop( L, 1); // out fifos keys
|
||||
if( fifo != NULL)
|
||||
if( fifo != NULL) // the key is known
|
||||
{
|
||||
lua_pushinteger( L, fifo->count); // out fifos keys count
|
||||
lua_rawset( L, 1); // out fifos keys
|
||||
}
|
||||
else
|
||||
else // the key is unknown
|
||||
{
|
||||
lua_pop( L, 1); // out fifos keys
|
||||
}
|
||||
}
|
||||
lua_pop( L, 1); // out
|
||||
}
|
||||
ASSERT_L( lua_gettop( L) == 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -526,97 +577,155 @@ int keepercall_count( lua_State* L)
|
||||
* bigger the pool, the less chances of unnecessary waits. Lindas map to the
|
||||
* keepers randomly, by a hash.
|
||||
*/
|
||||
static struct s_Keeper *GKeepers = NULL;
|
||||
static int GNbKeepers = 0;
|
||||
|
||||
#if HAVE_KEEPER_ATEXIT_DESINIT
|
||||
static void atexit_close_keepers( void)
|
||||
#else // HAVE_KEEPER_ATEXIT_DESINIT
|
||||
void close_keepers( void)
|
||||
#endif // HAVE_KEEPER_ATEXIT_DESINIT
|
||||
// called as __gc for the keepers array userdata
|
||||
void close_keepers( struct s_Universe* U, lua_State* L)
|
||||
{
|
||||
int i;
|
||||
int const nbKeepers = GNbKeepers;
|
||||
// NOTE: imagine some keeper state N+1 currently holds a linda that uses another keeper N, and a _gc that will make use of it
|
||||
// when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists
|
||||
// in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success
|
||||
GNbKeepers = 0;
|
||||
for( i = 0; i < nbKeepers; ++ i)
|
||||
if( U->keepers != NULL)
|
||||
{
|
||||
lua_State* L = GKeepers[i].L;
|
||||
GKeepers[i].L = NULL;
|
||||
lua_close( L);
|
||||
int i;
|
||||
int nbKeepers = U->keepers->nb_keepers;
|
||||
// NOTE: imagine some keeper state N+1 currently holds a linda that uses another keeper N, and a _gc that will make use of it
|
||||
// when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists
|
||||
// in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success
|
||||
// which is early-outed with a U->keepers->nbKeepers null-check
|
||||
U->keepers->nb_keepers = 0;
|
||||
for( i = 0; i < nbKeepers; ++ i)
|
||||
{
|
||||
lua_State* K = U->keepers->keeper_array[i].L;
|
||||
U->keepers->keeper_array[i].L = NULL;
|
||||
if( K != NULL)
|
||||
{
|
||||
lua_close( K);
|
||||
}
|
||||
else
|
||||
{
|
||||
// detected partial init: destroy only the mutexes that got initialized properly
|
||||
nbKeepers = i;
|
||||
}
|
||||
}
|
||||
for( i = 0; i < nbKeepers; ++ i)
|
||||
{
|
||||
MUTEX_FREE( &U->keepers->keeper_array[i].keeper_cs);
|
||||
}
|
||||
// free the keeper bookkeeping structure
|
||||
{
|
||||
void* allocUD;
|
||||
lua_Alloc allocF = lua_getallocf( L, &allocUD);
|
||||
allocF( allocUD, U->keepers, sizeof( struct s_Keepers) + (nbKeepers - 1) * sizeof(struct s_Keeper), 0);
|
||||
U->keepers = NULL;
|
||||
}
|
||||
}
|
||||
for( i = 0; i < nbKeepers; ++ i)
|
||||
{
|
||||
MUTEX_FREE( &GKeepers[i].lock_);
|
||||
}
|
||||
if( GKeepers != NULL)
|
||||
{
|
||||
free( GKeepers);
|
||||
}
|
||||
GKeepers = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize keeper states
|
||||
*
|
||||
* If there is a problem, return an error message (NULL for okay).
|
||||
*
|
||||
* Note: Any problems would be design flaws; the created Lua state is left
|
||||
* unclosed, because it does not really matter. In production code, this
|
||||
* function never fails.
|
||||
* settings table is at position 1 on the stack
|
||||
*/
|
||||
char const* init_keepers( lua_State* L)
|
||||
* Initialize keeper states
|
||||
*
|
||||
* If there is a problem, returns NULL and pushes the error message on the stack
|
||||
* else returns the keepers bookkeeping structure.
|
||||
*
|
||||
* Note: Any problems would be design flaws; the created Lua state is left
|
||||
* unclosed, because it does not really matter. In production code, this
|
||||
* function never fails.
|
||||
* settings table is at position 1 on the stack
|
||||
*/
|
||||
void init_keepers( struct s_Universe* U, lua_State* L)
|
||||
{
|
||||
int i;
|
||||
PROPAGATE_ALLOCF_PREP( L);
|
||||
int nb_keepers;
|
||||
void* allocUD;
|
||||
lua_Alloc allocF = lua_getallocf( L, &allocUD);
|
||||
|
||||
STACK_CHECK( L);
|
||||
lua_getfield( L, 1, "nb_keepers");
|
||||
GNbKeepers = (int) lua_tointeger( L, -1);
|
||||
lua_pop( L, 1);
|
||||
STACK_END( L, 0);
|
||||
assert( GNbKeepers >= 1);
|
||||
STACK_CHECK( L); // L K
|
||||
lua_getfield( L, 1, "nb_keepers"); // nb_keepers
|
||||
nb_keepers = (int) lua_tointeger( L, -1);
|
||||
lua_pop( L, 1); //
|
||||
assert( nb_keepers >= 1);
|
||||
|
||||
GKeepers = malloc( GNbKeepers * sizeof( struct s_Keeper));
|
||||
for( i = 0; i < GNbKeepers; ++ i)
|
||||
// struct s_Keepers contains an array of 1 s_Keeper, adjust for the actual number of keeper states
|
||||
{
|
||||
size_t const bytes = sizeof( struct s_Keepers) + (nb_keepers - 1) * sizeof(struct s_Keeper);
|
||||
U->keepers = (struct s_Keepers*) allocF( allocUD, NULL, 0, bytes);
|
||||
if( U->keepers == NULL)
|
||||
{
|
||||
(void) luaL_error( L, "init_keepers() failed while creating keeper array; out of memory");
|
||||
return;
|
||||
}
|
||||
memset( U->keepers, 0, bytes);
|
||||
U->keepers->nb_keepers = nb_keepers;
|
||||
}
|
||||
for( i = 0; i < nb_keepers; ++ i) // keepersUD
|
||||
{
|
||||
lua_State* K = PROPAGATE_ALLOCF_ALLOC();
|
||||
if( K == NULL)
|
||||
{
|
||||
(void) luaL_error( L, "init_keepers() failed while creating keeper state; out of memory");
|
||||
(void) luaL_error( L, "init_keepers() failed while creating keeper states; out of memory");
|
||||
return;
|
||||
}
|
||||
STACK_CHECK( K);
|
||||
|
||||
// to see VM name in Decoda debugger
|
||||
lua_pushliteral( K, "Keeper #");
|
||||
lua_pushinteger( K, i + 1);
|
||||
lua_concat( K, 2);
|
||||
lua_setglobal( K, "decoda_name");
|
||||
|
||||
// create the fifos table in the keeper state
|
||||
lua_pushlightuserdata( K, fifos_key);
|
||||
lua_newtable( K);
|
||||
lua_rawset( K, LUA_REGISTRYINDEX);
|
||||
|
||||
STACK_END( K, 0);
|
||||
U->keepers->keeper_array[i].L = K;
|
||||
// we can trigger a GC from inside keeper_call(), where a keeper is acquired
|
||||
// from there, GC can collect a linda, which would acquire the keeper again, and deadlock the thread.
|
||||
MUTEX_RECURSIVE_INIT( &GKeepers[i].lock_);
|
||||
GKeepers[i].L = K;
|
||||
// therefore, we need a recursive mutex.
|
||||
MUTEX_RECURSIVE_INIT( &U->keepers->keeper_array[i].keeper_cs);
|
||||
STACK_CHECK( K);
|
||||
|
||||
// copy the universe pointer in the keeper itself
|
||||
lua_pushlightuserdata( K, UNIVERSE_REGKEY);
|
||||
lua_pushlightuserdata( K, U);
|
||||
lua_rawset( K, LUA_REGISTRYINDEX);
|
||||
STACK_MID( K, 0);
|
||||
|
||||
// make sure 'package' is initialized in keeper states, so that we have require()
|
||||
// this because this is needed when transferring deep userdata object
|
||||
luaL_requiref( K, "package", luaopen_package, 1); // package
|
||||
lua_pop( K, 1); //
|
||||
STACK_MID( K, 0);
|
||||
serialize_require( U, K);
|
||||
STACK_MID( K, 0);
|
||||
|
||||
// copy package.path and package.cpath from the source state
|
||||
lua_getglobal( L, "package"); // "..." keepersUD package
|
||||
if( !lua_isnil( L, -1))
|
||||
{
|
||||
// when copying with mode eLM_ToKeeper, error message is pushed at the top of the stack, not raised immediately
|
||||
if( luaG_inter_copy_package( U, L, K, -1, eLM_ToKeeper))
|
||||
{
|
||||
// if something went wrong, the error message is at the top of the stack
|
||||
lua_remove( L, -2); // error_msg
|
||||
(void) lua_error( L);
|
||||
return;
|
||||
}
|
||||
}
|
||||
lua_pop( L, 1); //
|
||||
STACK_MID( L, 0);
|
||||
|
||||
// attempt to call on_state_create(), if we have one and it is a C function
|
||||
// (only support a C function because we can't transfer executable Lua code in keepers)
|
||||
// will raise an error in L in case of problem
|
||||
call_on_state_create( U, K, L, eLM_ToKeeper);
|
||||
|
||||
// to see VM name in Decoda debugger
|
||||
lua_pushliteral( K, "Keeper #"); // "Keeper #"
|
||||
lua_pushinteger( K, i + 1); // "Keeper #" n
|
||||
lua_concat( K, 2); // "Keeper #n"
|
||||
lua_setglobal( K, "decoda_name"); //
|
||||
|
||||
// create the fifos table in the keeper state
|
||||
lua_pushlightuserdata( K, fifos_key); // fifo_key
|
||||
lua_newtable( K); // fifo_key {}
|
||||
lua_rawset( K, LUA_REGISTRYINDEX); //
|
||||
|
||||
STACK_END( K, 0);
|
||||
}
|
||||
#if HAVE_KEEPER_ATEXIT_DESINIT
|
||||
atexit( atexit_close_keepers);
|
||||
#endif // HAVE_KEEPER_ATEXIT_DESINIT
|
||||
return NULL; // ok
|
||||
STACK_END( L, 0);
|
||||
}
|
||||
|
||||
struct s_Keeper* keeper_acquire( void const* ptr)
|
||||
struct s_Keeper* keeper_acquire( struct s_Keepers* keepers_, unsigned long magic_)
|
||||
{
|
||||
int const nbKeepers = keepers_->nb_keepers;
|
||||
// can be 0 if this happens during main state shutdown (lanes is being GC'ed -> no keepers)
|
||||
if( GNbKeepers == 0)
|
||||
if( nbKeepers == 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
@@ -629,10 +738,10 @@ struct s_Keeper* keeper_acquire( void const* ptr)
|
||||
* Pointers are often aligned by 8 or so - ignore the low order bits
|
||||
* have to cast to unsigned long to avoid compilation warnings about loss of data when converting pointer-to-integer
|
||||
*/
|
||||
unsigned int i = (unsigned int)(((unsigned long)(ptr) >> 3) % GNbKeepers);
|
||||
struct s_Keeper* K= &GKeepers[i];
|
||||
unsigned int i = (unsigned int)((magic_ >> KEEPER_MAGIC_SHIFT) % nbKeepers);
|
||||
struct s_Keeper* K = &keepers_->keeper_array[i];
|
||||
|
||||
MUTEX_LOCK( &K->lock_);
|
||||
MUTEX_LOCK( &K->keeper_cs);
|
||||
//++ K->count;
|
||||
return K;
|
||||
}
|
||||
@@ -641,29 +750,25 @@ struct s_Keeper* keeper_acquire( void const* ptr)
|
||||
void keeper_release( struct s_Keeper* K)
|
||||
{
|
||||
//-- K->count;
|
||||
if( K) MUTEX_UNLOCK( &K->lock_);
|
||||
if( K) MUTEX_UNLOCK( &K->keeper_cs);
|
||||
}
|
||||
|
||||
void keeper_toggle_nil_sentinels( lua_State* L, int _val_i, int _nil_to_sentinel)
|
||||
void keeper_toggle_nil_sentinels( lua_State* L, int val_i_, enum eLookupMode mode_)
|
||||
{
|
||||
int i, n = lua_gettop( L);
|
||||
/* We could use an empty table in 'keeper.lua' as the sentinel, but maybe
|
||||
* checking for a lightuserdata is faster. (any unique value will do -> take the address of some global of ours)
|
||||
*/
|
||||
void* nil_sentinel = &GNbKeepers;
|
||||
for( i = _val_i; i <= n; ++ i)
|
||||
for( i = val_i_; i <= n; ++ i)
|
||||
{
|
||||
if( _nil_to_sentinel)
|
||||
if( mode_ == eLM_ToKeeper)
|
||||
{
|
||||
if( lua_isnil( L, i))
|
||||
{
|
||||
lua_pushlightuserdata( L, nil_sentinel);
|
||||
lua_pushlightuserdata( L, NIL_SENTINEL);
|
||||
lua_replace( L, i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( lua_touserdata( L, i) == nil_sentinel)
|
||||
if( lua_touserdata( L, i) == NIL_SENTINEL)
|
||||
{
|
||||
lua_pushnil( L);
|
||||
lua_replace( L, i);
|
||||
@@ -681,7 +786,7 @@ void keeper_toggle_nil_sentinels( lua_State* L, int _val_i, int _nil_to_sentinel
|
||||
*
|
||||
* Returns: number of return values (pushed to 'L') or -1 in case of error
|
||||
*/
|
||||
int keeper_call( lua_State *K, keeper_api_t _func, lua_State *L, void *linda, uint_t starting_index)
|
||||
int keeper_call( struct s_Universe* U, lua_State* K, keeper_api_t func_, lua_State* L, void* linda, uint_t starting_index)
|
||||
{
|
||||
int const args = starting_index ? (lua_gettop( L) - starting_index + 1) : 0;
|
||||
int const Ktos = lua_gettop( K);
|
||||
@@ -689,11 +794,11 @@ int keeper_call( lua_State *K, keeper_api_t _func, lua_State *L, void *linda, ui
|
||||
|
||||
STACK_GROW( K, 2);
|
||||
|
||||
PUSH_KEEPER_FUNC( K, _func);
|
||||
PUSH_KEEPER_FUNC( K, func_);
|
||||
|
||||
lua_pushlightuserdata( K, linda);
|
||||
|
||||
if( (args == 0) || luaG_inter_copy( L, K, args, eLM_ToKeeper) == 0) // L->K
|
||||
if( (args == 0) || luaG_inter_copy( U, L, K, args, eLM_ToKeeper) == 0) // L->K
|
||||
{
|
||||
lua_call( K, 1 + args, LUA_MULTRET);
|
||||
|
||||
@@ -702,7 +807,7 @@ int keeper_call( lua_State *K, keeper_api_t _func, lua_State *L, void *linda, ui
|
||||
// this may interrupt a lane, causing the destruction of the underlying OS thread
|
||||
// after this, another lane making use of this keeper can get an error code from the mutex-locking function
|
||||
// when attempting to grab the mutex again (WINVER <= 0x400 does this, but locks just fine, I don't know about pthread)
|
||||
if( (retvals > 0) && luaG_inter_move( K, L, retvals, eLM_FromKeeper) != 0) // K->L
|
||||
if( (retvals > 0) && luaG_inter_move( U, K, L, retvals, eLM_FromKeeper) != 0) // K->L
|
||||
{
|
||||
retvals = -1;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#if !defined( __keeper_h__)
|
||||
#define __keeper_h__ 1
|
||||
|
||||
struct s_Keeper
|
||||
{
|
||||
MUTEX_T keeper_cs;
|
||||
lua_State* L;
|
||||
//int count;
|
||||
};
|
||||
|
||||
struct s_Keepers
|
||||
{
|
||||
int nb_keepers;
|
||||
struct s_Keeper keeper_array[1];
|
||||
};
|
||||
|
||||
void init_keepers( struct s_Universe* U, lua_State* L);
|
||||
void close_keepers( struct s_Universe* U, lua_State* L);
|
||||
|
||||
struct s_Keeper* keeper_acquire( struct s_Keepers* keepers_, unsigned long magic_);
|
||||
#define KEEPER_MAGIC_SHIFT 3
|
||||
void keeper_release( struct s_Keeper* K);
|
||||
void keeper_toggle_nil_sentinels( lua_State* L, int _val_i, enum eLookupMode const mode_);
|
||||
int keeper_push_linda_storage( struct s_Universe* U, lua_State* L, void* ptr, unsigned long magic_);
|
||||
|
||||
#define NIL_SENTINEL ((void*)keeper_toggle_nil_sentinels)
|
||||
|
||||
typedef lua_CFunction keeper_api_t;
|
||||
#define KEEPER_API( _op) keepercall_ ## _op
|
||||
#define PUSH_KEEPER_FUNC lua_pushcfunction
|
||||
// lua_Cfunctions to run inside a keeper state (formerly implemented in Lua)
|
||||
int keepercall_clear( lua_State* L);
|
||||
int keepercall_send( lua_State* L);
|
||||
int keepercall_receive( lua_State* L);
|
||||
int keepercall_receive_batched( lua_State* L);
|
||||
int keepercall_limit( lua_State* L);
|
||||
int keepercall_get( lua_State* L);
|
||||
int keepercall_set( lua_State* L);
|
||||
int keepercall_count( lua_State* L);
|
||||
|
||||
int keeper_call( struct s_Universe* U, lua_State* K, keeper_api_t _func, lua_State* L, void* linda, uint_t starting_index);
|
||||
|
||||
#endif // __keeper_h__
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* THREADING.C Copyright (c) 2007-08, Asko Kauppi
|
||||
* Copyright (C) 2009-13, Benoit Germain
|
||||
* Copyright (C) 2009-14, Benoit Germain
|
||||
*
|
||||
* Lua Lanes OS threading specific code.
|
||||
*
|
||||
@@ -12,7 +12,7 @@
|
||||
===============================================================================
|
||||
|
||||
Copyright (C) 2007-10 Asko Kauppi <akauppi@gmail.com>
|
||||
Copyright (C) 2009-13, Benoit Germain <bnt.germain@gmail.com>
|
||||
Copyright (C) 2009-14, Benoit Germain <bnt.germain@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -87,11 +87,13 @@ THE SOFTWARE.
|
||||
#if defined( PLATFORM_XBOX) || defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC)
|
||||
static void FAIL( char const* funcname, int rc)
|
||||
{
|
||||
fprintf( stderr, "%s() failed! (%d)\n", funcname, rc );
|
||||
char buf[256];
|
||||
FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM, NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256, NULL);
|
||||
fprintf( stderr, "%s() failed! [GetLastError() -> %d] '%s'", funcname, rc, buf);
|
||||
#ifdef _MSC_VER
|
||||
__debugbreak(); // give a chance to the debugger!
|
||||
__debugbreak(); // give a chance to the debugger!
|
||||
#endif // _MSC_VER
|
||||
abort();
|
||||
abort();
|
||||
}
|
||||
#endif // win32 build
|
||||
|
||||
@@ -246,7 +248,7 @@ static void prepare_timeout( struct timespec *ts, time_d abs_secs ) {
|
||||
|
||||
#if THREADAPI == THREADAPI_WINDOWS
|
||||
|
||||
#if WINVER <= 0x0400 // Windows NT4: Use Mutexes with Events
|
||||
#if _WIN32_WINNT < 0x0600 // CONDITION_VARIABLE aren't available
|
||||
//
|
||||
void MUTEX_INIT( MUTEX_T *ref ) {
|
||||
*ref= CreateMutex( NULL /*security attr*/, FALSE /*not locked*/, NULL );
|
||||
@@ -268,7 +270,7 @@ static void prepare_timeout( struct timespec *ts, time_d abs_secs ) {
|
||||
if (!ReleaseMutex(*ref))
|
||||
FAIL( "ReleaseMutex", GetLastError() );
|
||||
}
|
||||
#endif // Windows NT4
|
||||
#endif // CONDITION_VARIABLE aren't available
|
||||
|
||||
static int const gs_prio_remap[] =
|
||||
{
|
||||
@@ -296,11 +298,15 @@ void THREAD_CREATE( THREAD_T* ref, THREAD_RETURN_T (__stdcall *func)( void*), vo
|
||||
NULL // thread id (not used)
|
||||
);
|
||||
|
||||
if( h == INVALID_HANDLE_VALUE)
|
||||
if( h == NULL) // _beginthreadex returns 0L on failure instead of -1L (like _beginthread)
|
||||
{
|
||||
FAIL( "CreateThread", GetLastError());
|
||||
}
|
||||
|
||||
if (!SetThreadPriority( h, gs_prio_remap[prio + 3]))
|
||||
{
|
||||
FAIL( "SetThreadPriority", GetLastError());
|
||||
}
|
||||
|
||||
*ref = h;
|
||||
}
|
||||
@@ -378,7 +384,7 @@ bool_t THREAD_WAIT_IMPL( THREAD_T *ref, double secs)
|
||||
#endif // !__GNUC__
|
||||
}
|
||||
|
||||
#if WINVER <= 0x0400 // Windows NT4
|
||||
#if _WIN32_WINNT < 0x0600 // CONDITION_VARIABLE aren't available
|
||||
|
||||
void SIGNAL_INIT( SIGNAL_T* ref)
|
||||
{
|
||||
@@ -471,7 +477,7 @@ bool_t THREAD_WAIT_IMPL( THREAD_T *ref, double secs)
|
||||
FAIL( "WaitForSingleObject", GetLastError());
|
||||
}
|
||||
|
||||
#else // Windows Vista and above: condition variables exist, use them
|
||||
#else // CONDITION_VARIABLE are available, use them
|
||||
|
||||
//
|
||||
void SIGNAL_INIT( SIGNAL_T *ref )
|
||||
@@ -528,7 +534,7 @@ bool_t THREAD_WAIT_IMPL( THREAD_T *ref, double secs)
|
||||
WakeAllConditionVariable( ref);
|
||||
}
|
||||
|
||||
#endif // Windows Vista and above
|
||||
#endif // CONDITION_VARIABLE are available
|
||||
|
||||
#else // THREADAPI == THREADAPI_PTHREAD
|
||||
// PThread (Linux, OS X, ...)
|
||||
@@ -59,25 +59,33 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED };
|
||||
*/
|
||||
|
||||
#if THREADAPI == THREADAPI_WINDOWS
|
||||
#if defined ( PLATFORM_XBOX)
|
||||
#if defined( PLATFORM_XBOX)
|
||||
#include <xtl.h>
|
||||
#else // !PLATFORM_XBOX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
// 'SignalObjectAndWait' needs this (targets Windows 2000 and above)
|
||||
#ifndef _WIN32_WINNT // already defined by TDSM-Mingw64, so avoid a warning in that case
|
||||
#define _WIN32_WINNT 0x0400
|
||||
// CONDITION_VARIABLE needs version 0x0600+
|
||||
// _WIN32_WINNT value is already defined by MinGW, but not by MSVC
|
||||
#ifndef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0600
|
||||
#endif // _WIN32_WINNT
|
||||
#include <windows.h>
|
||||
#endif // !PLATFORM_XBOX
|
||||
#include <process.h>
|
||||
|
||||
/*
|
||||
#define XSTR(x) STR(x)
|
||||
#define STR(x) #x
|
||||
#pragma message( "The value of _WIN32_WINNT: " XSTR(_WIN32_WINNT))
|
||||
*/
|
||||
|
||||
// MSDN: http://msdn2.microsoft.com/en-us/library/ms684254.aspx
|
||||
//
|
||||
// CRITICAL_SECTION can be used for simple code protection. Mutexes are
|
||||
// needed for use with the SIGNAL system.
|
||||
//
|
||||
|
||||
#if WINVER <= 0x0400 // Windows NT4: use a signal
|
||||
#if _WIN32_WINNT < 0x0600 // CONDITION_VARIABLE aren't available, use a signal
|
||||
|
||||
typedef struct
|
||||
{
|
||||
CRITICAL_SECTION signalCS;
|
||||
@@ -94,7 +102,7 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED };
|
||||
void MUTEX_LOCK( MUTEX_T* ref);
|
||||
void MUTEX_UNLOCK( MUTEX_T* ref);
|
||||
|
||||
#else // Vista and above: use a condition variable
|
||||
#else // CONDITION_VARIABLE are available, use them
|
||||
|
||||
#define SIGNAL_T CONDITION_VARIABLE
|
||||
#define MUTEX_T CRITICAL_SECTION
|
||||
@@ -103,7 +111,7 @@ enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED };
|
||||
#define MUTEX_LOCK( ref) EnterCriticalSection( ref)
|
||||
#define MUTEX_UNLOCK( ref) LeaveCriticalSection( ref)
|
||||
|
||||
#endif // // Vista and above
|
||||
#endif // CONDITION_VARIABLE are available
|
||||
|
||||
#define MUTEX_RECURSIVE_INIT(ref) MUTEX_INIT(ref) /* always recursive in Win32 */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,14 +55,79 @@ void luaL_requiref (lua_State* L, const char* modname, lua_CFunction openf, int
|
||||
#define USE_DEBUG_SPEW 0
|
||||
#if USE_DEBUG_SPEW
|
||||
extern char const* debugspew_indent;
|
||||
extern int debugspew_indent_depth;
|
||||
#define INDENT_BEGIN "%.*s "
|
||||
#define INDENT_END , debugspew_indent_depth, debugspew_indent
|
||||
#define INDENT_END , (U ? U->debugspew_indent_depth : 0), debugspew_indent
|
||||
#define DEBUGSPEW_CODE(_code) _code
|
||||
#else // USE_DEBUG_SPEW
|
||||
#define DEBUGSPEW_CODE(_code)
|
||||
#endif // USE_DEBUG_SPEW
|
||||
|
||||
// ################################################################################################
|
||||
|
||||
/*
|
||||
* Do we want to activate full lane tracking feature? (EXPERIMENTAL)
|
||||
*/
|
||||
#define HAVE_LANE_TRACKING 1
|
||||
|
||||
// ################################################################################################
|
||||
|
||||
// this is pointed to by full userdata proxies, and allocated with malloc() to survive any lua_State lifetime
|
||||
typedef struct
|
||||
{
|
||||
volatile int refcount;
|
||||
void* deep;
|
||||
// when stored in a keeper state, the full userdata doesn't have a metatable, so we need direct access to the idfunc
|
||||
luaG_IdFunction idfunc;
|
||||
} DEEP_PRELUDE;
|
||||
|
||||
// ################################################################################################
|
||||
|
||||
// everything regarding the a Lanes universe is stored in that global structure
|
||||
// held as a full userdata in the master Lua state that required it for the first time
|
||||
// don't forget to initialize all members in LG_configure()
|
||||
struct s_Universe
|
||||
{
|
||||
// for verbose errors
|
||||
bool_t verboseErrors;
|
||||
|
||||
lua_CFunction on_state_create_func;
|
||||
|
||||
struct s_Keepers* keepers;
|
||||
|
||||
// Initialized by 'init_once_LOCKED()': the deep userdata Linda object
|
||||
// used for timers (each lane will get a proxy to this)
|
||||
volatile DEEP_PRELUDE* timer_deep; // = NULL
|
||||
|
||||
#if HAVE_LANE_TRACKING
|
||||
MUTEX_T tracking_cs;
|
||||
struct s_lane* volatile tracking_first; // will change to TRACKING_END if we want to activate tracking
|
||||
#endif // HAVE_LANE_TRACKING
|
||||
|
||||
MUTEX_T selfdestruct_cs;
|
||||
|
||||
// require() serialization
|
||||
MUTEX_T require_cs;
|
||||
|
||||
// Lock for reference counter inc/dec locks (to be initialized by outside code) TODO: get rid of this and use atomics instead!
|
||||
MUTEX_T deep_lock;
|
||||
MUTEX_T mtid_lock;
|
||||
|
||||
int last_mt_id;
|
||||
|
||||
#if USE_DEBUG_SPEW
|
||||
int debugspew_indent_depth;
|
||||
#endif // USE_DEBUG_SPEW
|
||||
|
||||
struct s_lane* volatile selfdestruct_first;
|
||||
// After a lane has removed itself from the chain, it still performs some processing.
|
||||
// The terminal desinit sequence should wait for all such processing to terminate before force-killing threads
|
||||
int volatile selfdestructing_count;
|
||||
};
|
||||
|
||||
struct s_Universe* get_universe( lua_State* L);
|
||||
extern void* const UNIVERSE_REGKEY;
|
||||
|
||||
// ################################################################################################
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define _ASSERT_L(lua,c) /*nothing*/
|
||||
@@ -98,13 +163,10 @@ extern int debugspew_indent_depth;
|
||||
|
||||
void luaG_dump( lua_State* L );
|
||||
|
||||
lua_State* luaG_newstate( lua_State* _from, int const _on_state_create, char const* libs);
|
||||
void luaG_copy_one_time_settings( lua_State* L, lua_State* L2, char const* name_);
|
||||
lua_State* luaG_newstate( struct s_Universe* U, lua_State* _from, char const* libs);
|
||||
void luaG_copy_one_time_settings( struct s_Universe* U, lua_State* L, lua_State* L2);
|
||||
|
||||
typedef struct {
|
||||
volatile int refcount;
|
||||
void *deep;
|
||||
} DEEP_PRELUDE;
|
||||
// ################################################################################################
|
||||
|
||||
enum eLookupMode
|
||||
{
|
||||
@@ -113,26 +175,23 @@ enum eLookupMode
|
||||
eLM_FromKeeper // send a function from a keeper state to a lane
|
||||
};
|
||||
|
||||
void luaG_push_proxy( lua_State *L, luaG_IdFunction idfunc, DEEP_PRELUDE *deep_userdata);
|
||||
void luaG_inter_copy_package( lua_State* L, lua_State* L2, int _idx, enum eLookupMode mode_);
|
||||
char const* push_deep_proxy( struct s_Universe* U, lua_State* L, DEEP_PRELUDE* prelude, enum eLookupMode mode_);
|
||||
void free_deep_prelude( lua_State* L, DEEP_PRELUDE* prelude_);
|
||||
|
||||
int luaG_inter_copy( lua_State *L, lua_State *L2, uint_t n, enum eLookupMode mode_);
|
||||
int luaG_inter_move( lua_State *L, lua_State *L2, uint_t n, enum eLookupMode mode_);
|
||||
int luaG_inter_copy_package( struct s_Universe* U, lua_State* L, lua_State* L2, int package_idx_, enum eLookupMode mode_);
|
||||
|
||||
int luaG_inter_copy( struct s_Universe* U, lua_State* L, lua_State* L2, uint_t n, enum eLookupMode mode_);
|
||||
int luaG_inter_move( struct s_Universe* U, lua_State* L, lua_State* L2, uint_t n, enum eLookupMode mode_);
|
||||
|
||||
int luaG_nameof( lua_State* L);
|
||||
int luaG_new_require( lua_State* L);
|
||||
|
||||
// Lock for reference counter inc/dec locks (to be initialized by outside code)
|
||||
//
|
||||
extern MUTEX_T deep_lock;
|
||||
extern MUTEX_T mtid_lock;
|
||||
|
||||
void populate_func_lookup_table( lua_State* L, int _i, char const* _name);
|
||||
void serialize_require( lua_State *L);
|
||||
extern MUTEX_T require_cs;
|
||||
void serialize_require( struct s_Universe* U, lua_State *L);
|
||||
void initialize_on_state_create( struct s_Universe* U, lua_State* L);
|
||||
void call_on_state_create( struct s_Universe* U, lua_State* L, lua_State* from_, enum eLookupMode mode_);
|
||||
|
||||
// for verbose errors
|
||||
extern bool_t GVerboseErrors;
|
||||
// ################################################################################################
|
||||
|
||||
extern char const* const CONFIG_REGKEY;
|
||||
extern char const* const LOOKUP_REGKEY;
|
||||
+35
-16
@@ -25,6 +25,11 @@ local function PRINT(...)
|
||||
end
|
||||
end
|
||||
|
||||
local gc_cb = function( name_, status_)
|
||||
PRINT( " ---> lane '" .. name_ .. "' collected with status " .. status_)
|
||||
end
|
||||
--gc_cb = nil
|
||||
|
||||
|
||||
---=== Local helpers ===---
|
||||
|
||||
@@ -71,7 +76,7 @@ local function task( a, b, c )
|
||||
return v, hey
|
||||
end
|
||||
|
||||
local task_launch= lanes_gen( "", { globals={hey=true} }, task )
|
||||
local task_launch= lanes_gen( "", { globals={hey=true}, gc_cb = gc_cb}, task )
|
||||
-- base stdlibs, normal priority
|
||||
|
||||
-- 'task_launch' is a factory of multithreaded tasks, we can launch several:
|
||||
@@ -100,6 +105,8 @@ assert( v2_hey == true )
|
||||
|
||||
assert( lane1.status == "done" )
|
||||
assert( lane1.status == "done" )
|
||||
lane1, lane2 = nil
|
||||
collectgarbage()
|
||||
|
||||
--##############################################################
|
||||
--##############################################################
|
||||
@@ -107,7 +114,7 @@ assert( lane1.status == "done" )
|
||||
|
||||
PRINT( "\n\n", "---=== Tasking (cancelling) ===---", "\n\n")
|
||||
|
||||
local task_launch2= lanes_gen( "", { cancelstep=100, globals={hey=true} }, task )
|
||||
local task_launch2= lanes_gen( "", { cancelstep=100, globals={hey=true}, gc_cb = gc_cb}, task )
|
||||
|
||||
local N=999999999
|
||||
local lane9= task_launch2(1,N,1) -- huuuuuuge...
|
||||
@@ -118,8 +125,7 @@ local st
|
||||
local t0= os.time()
|
||||
while os.time()-t0 < 5 do
|
||||
st= lane9.status
|
||||
local i
|
||||
io.stderr:write( (i==1) and st.." " or '.' )
|
||||
io.stderr:write( (i==1) and st.." " or '.' )
|
||||
if st~="pending" then break end
|
||||
end
|
||||
PRINT(" "..st)
|
||||
@@ -137,8 +143,7 @@ lane9:cancel()
|
||||
local t0= os.time()
|
||||
while os.time()-t0 < 5 do
|
||||
st= lane9.status
|
||||
local i
|
||||
io.stderr:write( (i==1) and st.." " or '.' )
|
||||
io.stderr:write( (i==1) and st.." " or '.' )
|
||||
if st~="running" then break end
|
||||
end
|
||||
PRINT(" "..st)
|
||||
@@ -198,7 +203,7 @@ PRINT( "\n\n", "---=== Communications ===---", "\n\n")
|
||||
local function WR(...) io.stderr:write(...) end
|
||||
|
||||
local chunk= function( linda )
|
||||
|
||||
set_debug_threadname "chunk"
|
||||
local function receive() return linda:receive( "->" ) end
|
||||
local function send(...) linda:send( "<-", ... ) end
|
||||
|
||||
@@ -228,7 +233,7 @@ local function PEEK() return linda:get("<-") end
|
||||
local function SEND(...) linda:send( "->", ... ) end
|
||||
local function RECEIVE() local k,v = linda:receive( 1, "<-" ) return v end
|
||||
|
||||
local t= lanes_gen("io",chunk)(linda) -- prepare & launch
|
||||
local t= lanes_gen("io", {gc_cb = gc_cb}, chunk)(linda) -- prepare & launch
|
||||
|
||||
SEND(1); WR( "1 sent\n" )
|
||||
SEND(2); WR( "2 sent\n" )
|
||||
@@ -258,6 +263,8 @@ assert( tables_match( a, {'a','b','c',d=10} ) )
|
||||
assert( PEEK() == nil )
|
||||
SEND(4)
|
||||
|
||||
t = nil
|
||||
collectgarbage()
|
||||
-- wait
|
||||
linda: receive( 1, "wait")
|
||||
|
||||
@@ -268,6 +275,7 @@ linda: receive( 1, "wait")
|
||||
PRINT( "\n\n", "---=== Stdlib naming ===---", "\n\n")
|
||||
|
||||
local function dump_g( _x)
|
||||
set_debug_threadname "dump_g"
|
||||
assert(print)
|
||||
print( "### dumping _G for '" .. _x .. "'")
|
||||
for k, v in pairs( _G) do
|
||||
@@ -277,6 +285,7 @@ local function dump_g( _x)
|
||||
end
|
||||
|
||||
local function io_os_f( _x)
|
||||
set_debug_threadname "io_os_f"
|
||||
assert(print)
|
||||
print( "### checking io and os libs existence for '" .. _x .. "'")
|
||||
assert(io)
|
||||
@@ -285,13 +294,14 @@ local function io_os_f( _x)
|
||||
end
|
||||
|
||||
local function coro_f( _x)
|
||||
set_debug_threadname "coro_f"
|
||||
assert(print)
|
||||
print( "### checking coroutine lib existence for '" .. _x .. "'")
|
||||
assert(coroutine)
|
||||
return true
|
||||
end
|
||||
|
||||
assert.fails( function() lanes_gen( "xxx", io_os_f ) end )
|
||||
assert.fails( function() lanes_gen( "xxx", {gc_cb = gc_cb}, io_os_f ) end )
|
||||
|
||||
local stdlib_naming_tests =
|
||||
{
|
||||
@@ -307,10 +317,12 @@ local stdlib_naming_tests =
|
||||
}
|
||||
|
||||
for _, t in ipairs( stdlib_naming_tests) do
|
||||
local f= lanes_gen( t[1], t[2]) -- any delimiter will do
|
||||
local f= lanes_gen( t[1], {gc_cb = gc_cb}, t[2]) -- any delimiter will do
|
||||
assert( f(t[1])[1] )
|
||||
end
|
||||
|
||||
collectgarbage()
|
||||
|
||||
--##############################################################
|
||||
--##############################################################
|
||||
--##############################################################
|
||||
@@ -319,9 +331,9 @@ PRINT( "\n\n", "---=== Comms criss cross ===---", "\n\n")
|
||||
|
||||
-- We make two identical lanes, which are using the same Linda channel.
|
||||
--
|
||||
local tc= lanes_gen( "io",
|
||||
local tc= lanes_gen( "io", {gc_cb = gc_cb},
|
||||
function( linda, ch_in, ch_out )
|
||||
|
||||
set_debug_threadname( "criss cross " .. ch_in .. " -> " .. ch_out)
|
||||
local function STAGE(str)
|
||||
io.stderr:write( ch_in..": "..str.."\n" )
|
||||
linda:send( nil, ch_out, str )
|
||||
@@ -340,6 +352,8 @@ local a,b= tc(linda, "A","B"), tc(linda, "B","A") -- launching two lanes, twis
|
||||
|
||||
local _= a[1],b[1] -- waits until they are both ready
|
||||
|
||||
a, b = nil
|
||||
collectgarbage()
|
||||
|
||||
--##############################################################
|
||||
--##############################################################
|
||||
@@ -380,7 +394,7 @@ local function chunk2( linda )
|
||||
end
|
||||
|
||||
local linda= lanes.linda()
|
||||
local t2= lanes_gen( "debug,string,io", chunk2 )(linda) -- prepare & launch
|
||||
local t2= lanes_gen( "debug,string,io", {gc_cb = gc_cb}, chunk2 )(linda) -- prepare & launch
|
||||
linda:send( "down", function(linda) linda:send( "up", "ready!" ) end,
|
||||
"ok" )
|
||||
-- wait to see if the tiny function gets executed
|
||||
@@ -413,8 +427,10 @@ PRINT( "\n\n", "---=== :join test ===---", "\n\n")
|
||||
-- (unless [1..n] has been read earlier, in which case it would seemingly
|
||||
-- work).
|
||||
|
||||
local S= lanes_gen( "table",
|
||||
local S= lanes_gen( "table", {gc_cb = gc_cb},
|
||||
function(arg)
|
||||
set_debug_threadname "join test lane"
|
||||
set_finalizer( function() end)
|
||||
aux= {}
|
||||
for i, v in ipairs(arg) do
|
||||
table.insert (aux, 1, v)
|
||||
@@ -424,11 +440,14 @@ local S= lanes_gen( "table",
|
||||
end )
|
||||
|
||||
h= S { 12, 13, 14 } -- execution starts, h[1..3] will get the return values
|
||||
|
||||
-- wait a bit so that the lane hasa chance to set its debug name
|
||||
linda:receive(0.5, "gloupti")
|
||||
print( "joining with '" .. h:get_debug_threadname() .. "'")
|
||||
local a,b,c,d= h:join()
|
||||
if h.status == "error" then
|
||||
print( "h error: " , a, b, c, d)
|
||||
print( h:get_debug_threadname(), "error: " , a, b, c, d)
|
||||
else
|
||||
print( h:get_debug_threadname(), a,b,c,d)
|
||||
assert(a==14)
|
||||
assert(b==13)
|
||||
assert(c==12)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
local lanes = require "lanes" .configure{ with_timers = false}
|
||||
|
||||
local linda = lanes.linda()
|
||||
|
||||
--####################################################################
|
||||
print "\n\n####################################################################\nbegin genlock & genatomic cancel test\n"
|
||||
|
||||
-- get a lock and a atomic operator
|
||||
local lock = lanes.genlock( linda, "lock", 1)
|
||||
local atomic = lanes.genatomic( linda, "atomic")
|
||||
|
||||
-- check that cancelled lindas give cancel_error as they should
|
||||
linda:cancel()
|
||||
assert( linda:get( "empty") == lanes.cancel_error)
|
||||
assert( lanes.genlock( linda, "any", 1) == lanes.cancel_error)
|
||||
assert( lanes.genatomic( linda, "any") == lanes.cancel_error)
|
||||
|
||||
-- check that lock and atomic functions return cancel_error if the linda was cancelled
|
||||
assert( lock( 1) == lanes.cancel_error)
|
||||
assert( lock( -1) == lanes.cancel_error)
|
||||
assert( atomic( 1) == lanes.cancel_error)
|
||||
|
||||
-- reset the linda so that the other tests work
|
||||
linda:cancel( "none")
|
||||
linda:limit( "lock", -1)
|
||||
linda:set( "lock")
|
||||
linda:limit( "atomic", -1)
|
||||
linda:set( "atomic")
|
||||
|
||||
--####################################################################
|
||||
|
||||
local laneBody = function( timeout_)
|
||||
set_finalizer( function( err, stk)
|
||||
if err == lanes.cancel_error then
|
||||
-- note that we don't get the cancel_error when running wrapped inside a protected call if it doesn't rethrow it
|
||||
print( " laneBody after cancel" )
|
||||
elseif err then
|
||||
print( " laneBody error: "..tostring(err))
|
||||
else
|
||||
print(" laneBody finalized")
|
||||
end
|
||||
end)
|
||||
|
||||
print( " entering lane with " .. tostring( timeout_) .. " timeout")
|
||||
repeat
|
||||
-- block-wait to be hard-cancelled
|
||||
print " lane calling receive()"
|
||||
local key, val = linda:receive( timeout_, "boob")
|
||||
print( " receive() -> ", lanes.cancel_error == key and "cancel_error" or tostring( key), tostring( val))
|
||||
until cancel_test() -- soft cancel self test
|
||||
print " shutting down after breaking out of loop"
|
||||
end
|
||||
|
||||
local protectedBody = function( ...)
|
||||
local ce = lanes.cancel_error
|
||||
local errorHandler = function( _msg)
|
||||
-- forward the message to the main thread that will display it with a popup
|
||||
print( " error handler got ", ce == _msg and "cancel_error"or tostring( _msg))
|
||||
return _msg
|
||||
end
|
||||
-- Lua 5.1 doesn't pass additional xpcall arguments to the called function
|
||||
-- therefore we need to create a closure that has no arguments but pulls everything from its upvalue
|
||||
local params = {...}
|
||||
local paramLessClosure = function() laneBody(table.unpack( params)) end
|
||||
local status, message = xpcall( paramLessClosure, errorHandler)
|
||||
if status == false then
|
||||
print( " error handler rethrowing '" .. (ce == message and "cancel_error"or tostring( message)) .. "'")
|
||||
-- if the error isn't rethrown, the lane's finalizer won't get it
|
||||
error( message)
|
||||
end
|
||||
end
|
||||
|
||||
--####################################################################
|
||||
|
||||
print "####################################################################\nbegin soft cancel test\n"
|
||||
h = lanes.gen("*", protectedBody)( 0.666)
|
||||
print "wait 3s"
|
||||
linda:receive( 3, "yeah")
|
||||
|
||||
-- soft cancel
|
||||
print "soft cancel with awakening"
|
||||
h:cancel( -1, true)
|
||||
|
||||
-- wait 10s: the lane will interrupt its loop and print the exit message
|
||||
print "wait 2s"
|
||||
linda:receive( 2, "yeah")
|
||||
|
||||
--####################################################################
|
||||
|
||||
print "\n\n####################################################################\nbegin hard cancel test\n"
|
||||
h = lanes.gen("*", protectedBody)()
|
||||
|
||||
-- wait 3s before cancelling the lane
|
||||
print "wait 3s"
|
||||
linda:receive( 3, "yeah")
|
||||
|
||||
-- hard cancel and wait 10s: the lane will be interrupted from inside its current linda:receive() and won't return from it
|
||||
print "hard cancel (always awakens)"
|
||||
h:cancel()
|
||||
|
||||
print "wait 5s"
|
||||
linda:receive( 5, "yeah")
|
||||
|
||||
--####################################################################
|
||||
|
||||
print "\n\n####################################################################\nbegin hard cancel test with unprotected lane body\n"
|
||||
h = lanes.gen("*", laneBody)()
|
||||
|
||||
-- wait 3s before cancelling the lane
|
||||
print "wait 3s"
|
||||
linda:receive( 3, "yeah")
|
||||
|
||||
-- hard cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it
|
||||
print "hard cancel (always awakens)"
|
||||
h:cancel()
|
||||
|
||||
print "wait 5s"
|
||||
linda:receive( 5, "yeah")
|
||||
|
||||
--####################################################################
|
||||
print "\n\n####################################################################\nbegin linda cancel test\n"
|
||||
h = lanes.gen("*", laneBody)()
|
||||
|
||||
-- wait 3s before cancelling the lane
|
||||
print "wait 3s"
|
||||
linda:receive( 3, "yeah")
|
||||
|
||||
-- linda cancel: the lane will be interrupted from inside its current linda:receive() and won't return from it
|
||||
print "linda cancel (always awakens the lane)"
|
||||
linda:cancel( "both")
|
||||
|
||||
print "wait 5s"
|
||||
linda:receive( 5, "yeah")
|
||||
|
||||
--####################################################################
|
||||
|
||||
print "\ndone"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Test file that should get removed.
|
||||
@@ -4,7 +4,7 @@
|
||||
-- Test program for Lua Lanes
|
||||
--
|
||||
|
||||
local lanes = require "lanes".configure{ with_timers = false}
|
||||
local lanes = require "lanes".configure{ with_timers = false, nb_keepers = 200}
|
||||
|
||||
local function keeper(linda)
|
||||
local mt= {
|
||||
@@ -19,10 +19,10 @@ local function keeper(linda)
|
||||
end
|
||||
|
||||
--
|
||||
local lindaA= lanes.linda()
|
||||
local lindaA= lanes.linda( "A", 1)
|
||||
local A= keeper( lindaA )
|
||||
|
||||
local lindaB= lanes.linda()
|
||||
local lindaB= lanes.linda( "B", 2)
|
||||
local B= keeper( lindaB )
|
||||
|
||||
A.some= 1
|
||||
|
||||
@@ -6,7 +6,9 @@ local table_unpack = unpack or table.unpack
|
||||
|
||||
-- this lane eats items in the linda one by one
|
||||
local eater = function( l, loop)
|
||||
local key, val = l:receive( "go")
|
||||
-- wait for start signal
|
||||
l:receive( "go")
|
||||
-- eat data one by one
|
||||
for i = 1, loop do
|
||||
local val, key = l:receive( "key")
|
||||
--print( val)
|
||||
@@ -18,7 +20,9 @@ end
|
||||
|
||||
-- this lane eats items in the linda in batches
|
||||
local batched = function( l, loop, batch)
|
||||
local key, val = l:receive( "go")
|
||||
-- wait for start signal
|
||||
l:receive( "go")
|
||||
-- eat data in batches
|
||||
for i = 1, loop/batch do
|
||||
l:receive( l.batched, "key", batch)
|
||||
end
|
||||
@@ -27,19 +31,20 @@ local batched = function( l, loop, batch)
|
||||
print( val)
|
||||
end
|
||||
|
||||
local lane_eater_gen = lanes.gen( "*", eater)
|
||||
local lane_batched_gen = lanes.gen( "*", batched)
|
||||
local lane_eater_gen = lanes.gen( "*", {priority = 3}, eater)
|
||||
local lane_batched_gen = lanes.gen( "*", {priority = 3}, batched)
|
||||
|
||||
-- main thread writes data while a lane reads it
|
||||
local function ziva( preloop, loop, batch)
|
||||
-- prefill the linda a bit to increase fifo stress
|
||||
local top = math.max( preloop, loop)
|
||||
local l, lane = lanes.linda()
|
||||
local t1 = os.time()
|
||||
local t1 = lanes.now_secs()
|
||||
for i = 1, preloop do
|
||||
l:send( "key", i)
|
||||
end
|
||||
print( l:count( "key"))
|
||||
if batch then
|
||||
print( "stored " .. l:count( "key") .. " items in the linda before starting consumer lane")
|
||||
if batch > 0 then
|
||||
if l.batched then
|
||||
lane = lane_batched_gen( l, top, batch)
|
||||
else
|
||||
@@ -49,46 +54,47 @@ local function ziva( preloop, loop, batch)
|
||||
else
|
||||
lane = lane_eater_gen( l, top)
|
||||
end
|
||||
-- tell the lanes they can start eating data
|
||||
l:send("go", "go")
|
||||
-- tell the consumer lane it can start eating data
|
||||
l:send( "go", true)
|
||||
-- send the remainder of the elements while they are consumed
|
||||
-- create a function that can send several values in one shot
|
||||
batch = math.max( batch, 1)
|
||||
local batch_values = {}
|
||||
for i = 1, batch do
|
||||
table.insert( batch_values, i)
|
||||
end
|
||||
local batch_send = function()
|
||||
l:send( "key", table_unpack( batch_values))
|
||||
end
|
||||
if loop > preloop then
|
||||
for i = preloop + 1, loop do
|
||||
l:send( "key", i)
|
||||
for i = preloop + 1, loop, batch do
|
||||
batch_send()
|
||||
end
|
||||
end
|
||||
l:send( "done" ,"are you happy?")
|
||||
lane:join()
|
||||
return os.difftime(os.time(), t1)
|
||||
return lanes.now_secs() - t1
|
||||
end
|
||||
|
||||
local tests =
|
||||
local tests1 =
|
||||
{
|
||||
--[[{ 2000000, 0},
|
||||
{ 3000000, 0},
|
||||
{ 4000000, 0},
|
||||
{ 5000000, 0},
|
||||
{ 6000000, 0},]]
|
||||
--[[{ 1000000, 2000000},
|
||||
{ 2000000, 3000000},
|
||||
{ 3000000, 4000000},
|
||||
{ 4000000, 5000000},
|
||||
{ 5000000, 6000000},]]
|
||||
--[[{ 4000000, 0},
|
||||
{ 4000000, 0, 1},
|
||||
{ 4000000, 0, 2},
|
||||
{ 4000000, 0, 3},
|
||||
{ 4000000, 0, 5},
|
||||
{ 4000000, 0, 8},
|
||||
{ 4000000, 0, 13},
|
||||
{ 4000000, 0, 21},
|
||||
{ 4000000, 0, 44},]]
|
||||
--[[
|
||||
{ 10000, 2000000, 0},
|
||||
{ 10000, 2000000, 1},
|
||||
{ 10000, 2000000, 2},
|
||||
{ 10000, 2000000, 3},
|
||||
{ 10000, 2000000, 5},
|
||||
{ 10000, 2000000, 8},
|
||||
{ 10000, 2000000, 13},
|
||||
{ 10000, 2000000, 21},
|
||||
{ 10000, 2000000, 44},
|
||||
--]]
|
||||
}
|
||||
print "tests #1"
|
||||
for k, v in pairs( tests) do
|
||||
print "############################################\ntests #1"
|
||||
for k, v in pairs( tests1) do
|
||||
local pre, loop, batch = v[1], v[2], v[3]
|
||||
print( "testing", pre, loop, batch)
|
||||
print( pre, loop, batch, "duration = " .. ziva( pre, loop, batch))
|
||||
print( pre, loop, batch, "duration = " .. ziva( pre, loop, batch) .. "\n")
|
||||
end
|
||||
|
||||
--[[
|
||||
@@ -124,6 +130,7 @@ end
|
||||
ziva( 4000000, 0, 44) -> 2s
|
||||
]]
|
||||
|
||||
-- sequential write/read (no parallelization involved)
|
||||
local function ziva2( preloop, loop, batch)
|
||||
local l = lanes.linda()
|
||||
-- prefill the linda a bit to increase fifo stress
|
||||
@@ -149,11 +156,12 @@ local function ziva2( preloop, loop, batch)
|
||||
l:receive( "key")
|
||||
end
|
||||
end
|
||||
local t1 = os.time()
|
||||
local t1 = lanes.now_secs()
|
||||
-- first, prime the linda with some data
|
||||
for i = 1, preloop, step do
|
||||
batch_send()
|
||||
end
|
||||
print( "stored " .. (l:count( "key") or 0) .. " items in the linda before starting consumer lane")
|
||||
-- loop that alternatively sends and reads data off the linda
|
||||
if loop > preloop then
|
||||
for i = preloop + 1, loop, step do
|
||||
@@ -165,21 +173,13 @@ local function ziva2( preloop, loop, batch)
|
||||
for i = 1, preloop, step do
|
||||
batch_read()
|
||||
end
|
||||
return os.difftime(os.time(), t1)
|
||||
return lanes.now_secs() - t1
|
||||
end
|
||||
|
||||
local tests2 =
|
||||
{
|
||||
--[[{ 2000000, 0},
|
||||
{ 3000000, 0},
|
||||
{ 4000000, 0},
|
||||
{ 5000000, 0},
|
||||
{ 6000000, 0},
|
||||
{ 1000000, 2000000},
|
||||
{ 2000000, 3000000},
|
||||
{ 3000000, 4000000},
|
||||
{ 4000000, 5000000},
|
||||
{ 5000000, 6000000},]]
|
||||
-- prefill, then consume everything
|
||||
--[[
|
||||
{ 4000000, 0},
|
||||
{ 4000000, 0, 1},
|
||||
{ 4000000, 0, 2},
|
||||
@@ -189,44 +189,22 @@ local tests2 =
|
||||
{ 4000000, 0, 13},
|
||||
{ 4000000, 0, 21},
|
||||
{ 4000000, 0, 44},
|
||||
--]]
|
||||
-- alternatively fill and consume
|
||||
{ 0, 4000000},
|
||||
{ 0, 4000000, 1},
|
||||
{ 0, 4000000, 2},
|
||||
{ 0, 4000000, 3},
|
||||
{ 0, 4000000, 5},
|
||||
{ 0, 4000000, 8},
|
||||
{ 0, 4000000, 13},
|
||||
{ 0, 4000000, 21},
|
||||
{ 0, 4000000, 44},
|
||||
}
|
||||
|
||||
print "tests #2"
|
||||
print "\n############################################\ntests #2"
|
||||
for k, v in pairs( tests2) do
|
||||
local pre, loop, batch = v[1], v[2], v[3]
|
||||
print( "testing", pre, loop, batch)
|
||||
print( pre, loop, batch, "duration = " .. ziva2( pre, loop, batch))
|
||||
print( pre, loop, batch, "duration = " .. ziva2( pre, loop, batch) .. "\n")
|
||||
end
|
||||
|
||||
--[[
|
||||
V 2.1.0:
|
||||
ziva( 20000, 0) -> 3s ziva( 10000, 20000) -> 3s
|
||||
ziva( 30000, 0) -> 8s ziva( 20000, 30000) -> 7s
|
||||
ziva( 40000, 0) -> 15s ziva( 30000, 40000) -> 14s
|
||||
ziva( 50000, 0) -> 24s ziva( 40000, 50000) -> 22s
|
||||
ziva( 60000, 0) -> 34s ziva( 50000, 60000) -> 33s
|
||||
|
||||
SIMPLIFIED:
|
||||
ziva( 20000, 0) -> 4s ziva( 10000, 20000) -> 3s
|
||||
ziva( 30000, 0) -> 8s ziva( 20000, 30000) -> 7s
|
||||
ziva( 40000, 0) -> 14s ziva( 30000, 40000) -> 14s
|
||||
ziva( 50000, 0) -> 23s ziva( 40000, 50000) -> 22s
|
||||
ziva( 60000, 0) -> 33s ziva( 50000, 60000) -> 32s
|
||||
|
||||
FIFO:
|
||||
ziva( 2000000, 0) -> 9s ziva( 1000000, 2000000) -> 14s
|
||||
ziva( 3000000, 0) -> 14s ziva( 2000000, 3000000) -> 23s
|
||||
ziva( 4000000, 0) -> 19s ziva( 3000000, 4000000) -> 23s
|
||||
ziva( 5000000, 0) -> 24s ziva( 4000000, 5000000) -> 32s
|
||||
ziva( 6000000, 0) -> 29s ziva( 5000000, 6000000) -> 33s
|
||||
|
||||
FIFO BATCHED:
|
||||
ziva( 4000000, 0, 1) -> 19s
|
||||
ziva( 4000000, 0, 2) -> 11s
|
||||
ziva( 4000000, 0, 3) -> s
|
||||
ziva( 4000000, 0, 5) -> s
|
||||
ziva( 4000000, 0, 8) -> s
|
||||
ziva( 4000000, 0, 13) -> s
|
||||
ziva( 4000000, 0, 21) -> s
|
||||
ziva( 4000000, 0, 44) -> s
|
||||
]]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
lanes = require "lanes".configure()
|
||||
|
||||
print( lanes.nameof( {}))
|
||||
print( lanes.nameof( string.sub))
|
||||
print( lanes.nameof( print))
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
local MSYS= os.getenv("OSTYPE")=="msys"
|
||||
|
||||
|
||||
local lanes = require "lanes"
|
||||
lanes.configure()
|
||||
local lanes = require "lanes".configure{ with_timers = false}
|
||||
|
||||
local m= require "argtable"
|
||||
local argtable= assert( m.argtable )
|
||||
@@ -36,7 +35,7 @@ local argtable= assert( m.argtable )
|
||||
local N= 1000 -- threads/loops to use
|
||||
local M= 1000 -- sieves from 1..M
|
||||
local PLAIN= false -- single threaded (true) or using Lanes (false)
|
||||
local SINGLE= false -- cores to use (false / 1..n)
|
||||
local SINGLE= 0 -- cores to use (0 / 1..n)
|
||||
local TIME= false -- use Lua for the timing
|
||||
local PRIO_ODD, PRIO_EVEN -- -3..+3
|
||||
|
||||
@@ -63,7 +62,7 @@ end
|
||||
for k,v in pairs( argtable(...) ) do
|
||||
if k==1 then N= tonumber(v) or HELP()
|
||||
elseif k=="plain" then PLAIN= true
|
||||
elseif k=="single" then SINGLE= v -- true/number
|
||||
elseif k=="single" then SINGLE= v -- number
|
||||
elseif k=="time" then TIME= true
|
||||
elseif k=="prio" then PRIO_ODD, PRIO_EVEN= prio_param(v)
|
||||
else HELP()
|
||||
@@ -104,7 +103,7 @@ local function sieve_lane(N,id)
|
||||
while 1 do
|
||||
local n = g()
|
||||
if n == nil then return end
|
||||
if math.mod(n, p) ~= 0 then coroutine.yield(n) end
|
||||
if math.fmod(n, p) ~= 0 then coroutine.yield(n) end
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -138,7 +137,7 @@ local f_odd= lanes.gen( "base,coroutine,math,table,io", -- "*" = all
|
||||
|
||||
io.stderr:write( "*** Counting primes 1.."..M.." "..N.." times ***\n\n" )
|
||||
|
||||
local t0= TIME and os.time()
|
||||
local t0= TIME and lanes.now_secs()
|
||||
|
||||
if PLAIN then
|
||||
io.stderr:write( "Plain (no multithreading):\n" )
|
||||
@@ -148,9 +147,9 @@ if PLAIN then
|
||||
assert( type(tmp)=="table" and tmp[1]==2 and tmp[168]==997 )
|
||||
end
|
||||
else
|
||||
if SINGLE then
|
||||
if SINGLE > 0 then
|
||||
io.stderr:write( (tonumber(SINGLE) and SINGLE or 1) .. " core(s):\n" )
|
||||
lanes.single(SINGLE) -- limit to N cores (just OS X)
|
||||
lanes.set_singlethreaded(SINGLE) -- limit to N cores (just OS X)
|
||||
else
|
||||
io.stderr:write( "Multi core:\n" )
|
||||
end
|
||||
@@ -177,7 +176,7 @@ end
|
||||
io.stderr:write "\n"
|
||||
|
||||
if TIME then
|
||||
local t= os.time() - t0
|
||||
local t= lanes.now_secs() - t0
|
||||
io.stderr:write( "*** TIMING: "..t.." seconds ***\n" )
|
||||
end
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ local function func( depth )
|
||||
local lanes = require "lanes"
|
||||
-- lanes.configure() is available only at the first require()
|
||||
if lanes.configure then
|
||||
lanes = lanes.configure()
|
||||
lanes = lanes.configure{with_timers = false}
|
||||
end
|
||||
local lane= lanes.gen("*", func)( depth+1 )
|
||||
return lane[1]
|
||||
|
||||
@@ -128,7 +128,6 @@ lanes.configure = function( settings_)
|
||||
end
|
||||
local settings = core.configure and core.configure( params_checker( settings_)) or core.settings
|
||||
local thread_new = assert( core.thread_new)
|
||||
local set_singlethreaded = assert( core.set_singlethreaded)
|
||||
local max_prio = assert( core.max_prio)
|
||||
|
||||
lanes.ABOUT =
|
||||
@@ -197,7 +196,10 @@ end
|
||||
--
|
||||
-- .globals: table of globals to set for a new thread (passed by value)
|
||||
--
|
||||
-- .required: table of packages to require
|
||||
-- .required: table of packages to require
|
||||
--
|
||||
-- .gc_cb: function called when the lane handle is collected
|
||||
--
|
||||
-- ... (more options may be introduced later) ...
|
||||
--
|
||||
-- Calling with a function parameter ('lane_func') ends the string/table
|
||||
@@ -273,10 +275,11 @@ local function gen( ... )
|
||||
end
|
||||
end
|
||||
|
||||
local prio, cs, g_tbl, package_tbl, required
|
||||
local prio, cs, g_tbl, package_tbl, required, gc_cb
|
||||
|
||||
for k,v in pairs(opt) do
|
||||
if k=="priority" then prio= v
|
||||
if k == "priority" then
|
||||
prio = (type( v) == "number") and v or error( "Bad 'prio' option: expecting number, got " .. type( v), lev)
|
||||
elseif k=="cancelstep" then
|
||||
cs = (v==true) and 100 or
|
||||
(v==false) and 0 or
|
||||
@@ -287,6 +290,8 @@ local function gen( ... )
|
||||
package_tbl = (type( v) == "table") and v or error( "Bad package: " .. tostring( v), lev)
|
||||
elseif k=="required" then
|
||||
required= (type( v) == "table") and v or error( "Bad 'required' option: expecting table, got " .. type( v), lev)
|
||||
elseif k == "gc_cb" then
|
||||
gc_cb = (type( v) == "function") and v or error( "Bad 'gc_cb' option: expecting function, got " .. type( v), lev)
|
||||
--..
|
||||
elseif k==1 then error( "unkeyed option: ".. tostring(v), lev )
|
||||
else error( "Bad option: ".. tostring(k), lev )
|
||||
@@ -297,21 +302,10 @@ local function gen( ... )
|
||||
-- Lane generator
|
||||
--
|
||||
return function(...)
|
||||
return thread_new( func, libs, settings.on_state_create, cs, prio, g_tbl, package_tbl, required, ...) -- args
|
||||
return thread_new( func, libs, cs, prio, g_tbl, package_tbl, required, gc_cb, ...) -- args
|
||||
end
|
||||
end
|
||||
|
||||
---=== Lindas ===---
|
||||
|
||||
-- We let the C code attach methods to userdata directly
|
||||
|
||||
-----
|
||||
-- lanes.linda(["name"]) -> linda_ud
|
||||
--
|
||||
-- PUBLIC LANES API
|
||||
local linda = core.linda
|
||||
|
||||
|
||||
---=== Timers ===---
|
||||
|
||||
-- PUBLIC LANES API
|
||||
@@ -580,13 +574,25 @@ end
|
||||
|
||||
end -- settings.with_timers
|
||||
|
||||
-- avoid pulling the whole core module as upvalue when cancel_error is enough
|
||||
local cancel_error = assert( core.cancel_error)
|
||||
|
||||
---=== Lock & atomic generators ===---
|
||||
|
||||
-- These functions are just surface sugar, but make solutions easier to read.
|
||||
-- Not many applications should even need explicit locks or atomic counters.
|
||||
|
||||
--
|
||||
-- lock_f= lanes.genlock( linda_h, key [,N_uint=1] )
|
||||
-- [true [, ...]= trues(uint)
|
||||
--
|
||||
local function trues( n)
|
||||
if n > 0 then
|
||||
return true, trues( n - 1)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- lock_f = lanes.genlock( linda_h, key [,N_uint=1] )
|
||||
--
|
||||
-- = lock_f( +M ) -- acquire M
|
||||
-- ...locked...
|
||||
@@ -597,16 +603,10 @@ end -- settings.with_timers
|
||||
--
|
||||
-- PUBLIC LANES API
|
||||
local genlock = function( linda, key, N)
|
||||
linda:limit( key, N)
|
||||
linda:set( key, nil) -- clears existing data
|
||||
|
||||
--
|
||||
-- [true [, ...]= trues(uint)
|
||||
--
|
||||
local function trues( n)
|
||||
if n > 0 then
|
||||
return true, trues( n - 1)
|
||||
end
|
||||
-- clear existing data and set the limit
|
||||
N = N or 1
|
||||
if linda:set( key) == cancel_error or linda:limit( key, N) == cancel_error then
|
||||
return cancel_error
|
||||
end
|
||||
|
||||
-- use an optimized version for case N == 1
|
||||
@@ -618,7 +618,8 @@ local genlock = function( linda, key, N)
|
||||
return linda:send( timeout, key, true) -- suspends until been able to push them
|
||||
else
|
||||
local k = linda:receive( nil, key)
|
||||
return k and true or false
|
||||
-- propagate cancel_error if we got it, else return true or false
|
||||
return k and ((k ~= cancel_error) and true or k) or false
|
||||
end
|
||||
end
|
||||
or
|
||||
@@ -629,34 +630,45 @@ local genlock = function( linda, key, N)
|
||||
return linda:send( timeout, key, trues(M)) -- suspends until been able to push them
|
||||
else
|
||||
local k = linda:receive( nil, linda.batched, key, -M)
|
||||
return k and true or false
|
||||
-- propagate cancel_error if we got it, else return true or false
|
||||
return k and ((k ~= cancel_error) and true or k) or false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- atomic_f= lanes.genatomic( linda_h, key [,initial_num=0.0] )
|
||||
--
|
||||
-- int= atomic_f( [diff_num=1.0] )
|
||||
--
|
||||
-- Returns an access function that allows atomic increment/decrement of the
|
||||
-- number in 'key'.
|
||||
--
|
||||
-- PUBLIC LANES API
|
||||
local function genatomic( linda, key, initial_val )
|
||||
linda:limit(key,2) -- value [,true]
|
||||
linda:set(key,initial_val or 0.0) -- clears existing data (also queue)
|
||||
--
|
||||
-- atomic_f = lanes.genatomic( linda_h, key [,initial_num=0.0])
|
||||
--
|
||||
-- int|cancel_error = atomic_f( [diff_num = 1.0])
|
||||
--
|
||||
-- Returns an access function that allows atomic increment/decrement of the
|
||||
-- number in 'key'.
|
||||
--
|
||||
-- PUBLIC LANES API
|
||||
local genatomic = function( linda, key, initial_val)
|
||||
-- clears existing data (also queue). the slot may contain the stored value, and an additional boolean value
|
||||
if linda:limit( key, 2) == cancel_error or linda:set( key, initial_val or 0.0) == cancel_error then
|
||||
return cancel_error
|
||||
end
|
||||
|
||||
return
|
||||
function(diff)
|
||||
-- 'nil' allows 'key' to be numeric
|
||||
linda:send( nil, key, true ) -- suspends until our 'true' is in
|
||||
local val= linda:get(key) + (diff or 1.0)
|
||||
linda:set( key, val ) -- releases the lock, by emptying queue
|
||||
return val
|
||||
end
|
||||
end
|
||||
return function( diff)
|
||||
-- 'nil' allows 'key' to be numeric
|
||||
-- suspends until our 'true' is in
|
||||
if linda:send( nil, key, true) == cancel_error then
|
||||
return cancel_error
|
||||
end
|
||||
local val = linda:get( key)
|
||||
if val ~= cancel_error then
|
||||
val = val + (diff or 1.0)
|
||||
-- set() releases the lock by emptying queue
|
||||
if linda:set( key, val) == cancel_error then
|
||||
val = cancel_error
|
||||
end
|
||||
end
|
||||
return val
|
||||
end
|
||||
end
|
||||
|
||||
-- activate full interface
|
||||
lanes.require = core.require
|
||||
@@ -664,6 +676,7 @@ end
|
||||
lanes.linda = core.linda
|
||||
lanes.cancel_error = core.cancel_error
|
||||
lanes.nameof = core.nameof
|
||||
lanes.set_singlethreaded = core.set_singlethreaded
|
||||
lanes.threads = core.threads or function() error "lane tracking is not available" end -- core.threads isn't registered if settings.track_lanes is false
|
||||
lanes.set_thread_priority = core.set_thread_priority
|
||||
lanes.timer = timer
|
||||
|
||||
Reference in New Issue
Block a user