upgrade to 3.9.6 & osx builds

This commit is contained in:
Cosmin Apreutesei
2014-03-23 11:14:38 +02:00
parent 1e97c7e94c
commit b37ecb9e61
26 changed files with 2417 additions and 1810 deletions
+5 -3
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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
+2
View File
@@ -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
+2
View File
@@ -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
-41
View File
@@ -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__
+10 -2
View File
@@ -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__
+259 -154
View File
@@ -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;
}
+43
View File
@@ -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
+80 -21
View File
@@ -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
View File
@@ -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)
+138
View File
@@ -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
View File
@@ -1 +0,0 @@
Test file that should get removed.
+3 -3
View File
@@ -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
+60 -82
View File
@@ -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
View File
@@ -1,4 +1,5 @@
lanes = require "lanes".configure()
print( lanes.nameof( {}))
print( lanes.nameof( string.sub))
print( lanes.nameof( print))
+8 -9
View File
@@ -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
+1 -1
View File
@@ -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]
+63 -50
View File
@@ -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