diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000000000000000000000000000000000000..7c29859c8a7a64652532e031e28a3bc3b6cbae75 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,4 @@ +I (Nicolas Casalini), gladly accepts contributions to the engine code either +bugfixes or new features (although I feel no obligations to accept them all). + +To contribute diff --git a/game/thirdparty/lanes.lua b/game/thirdparty/lanes.lua new file mode 100644 index 0000000000000000000000000000000000000000..28b19cf046b93f9a66c3381ad4d511f5c7a89fe0 --- /dev/null +++ b/game/thirdparty/lanes.lua @@ -0,0 +1,611 @@ +-- +-- LANES.LUA +-- +-- Multithreading and -core support for Lua +-- +-- Author: Asko Kauppi <akauppi@gmail.com> +-- +-- History: +-- Jun-08 AKa: major revise +-- 15-May-07 AKa: pthread_join():less version, some speedup & ability to +-- handle more threads (~ 8000-9000, up from ~ 5000) +-- 26-Feb-07 AKa: serialization working (C side) +-- 17-Sep-06 AKa: started the module (serialization) +-- +--[[ +=============================================================================== + +Copyright (C) 2007-08 Asko Kauppi <akauppi@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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=============================================================================== +]]-- + +module( "lanes", package.seeall ) + +--require "lua51-lanes" +assert( type(lanes)=="table" ) + +local mm= lanes + +local linda_id= assert( mm.linda_id ) + +local thread_new= assert(mm.thread_new) +local thread_status= assert(mm.thread_status) +local thread_join= assert(mm.thread_join) +local thread_cancel= assert(mm.thread_cancel) + +local _single= assert(mm._single) +local _version= assert(mm._version) + +local _deep_userdata= assert(mm._deep_userdata) + +local now_secs= assert( mm.now_secs ) +local wakeup_conv= assert( mm.wakeup_conv ) +local timer_gateway= assert( mm.timer_gateway ) + +local max_prio= assert( mm.max_prio ) + +-- This check is for sublanes requiring Lanes +-- +-- TBD: We could also have the C level expose 'string.gmatch' for us. But this is simpler. +-- +if not string then + error( "To use 'lanes', you will also need to have 'string' available.", 2 ) +end + +-- +-- Cache globals for code that might run under sandboxing +-- +local assert= assert +local string_gmatch= assert( string.gmatch ) +local select= assert( select ) +local type= assert( type ) +local pairs= assert( pairs ) +local tostring= assert( tostring ) +local error= assert( error ) +local setmetatable= assert( setmetatable ) +local rawget= assert( rawget ) + +ABOUT= +{ + author= "Asko Kauppi <akauppi@gmail.com>", + description= "Running multiple Lua states in parallel", + license= "MIT/X11", + copyright= "Copyright (c) 2007-08, Asko Kauppi", + version= _version, +} + + +-- Making copies of necessary system libs will pass them on as upvalues; +-- only the first state doing "require 'lanes'" will need to have 'string' +-- and 'table' visible. +-- +local function WR(str) + io.stderr:write( str.."\n" ) +end + +local function DUMP( tbl ) + if not tbl then return end + local str="" + for k,v in pairs(tbl) do + str= str..k.."="..tostring(v).."\n" + end + WR(str) +end + + +---=== Laning ===--- + +-- lane_h[1..n]: lane results, same as via 'lane_h:join()' +-- lane_h[0]: can be read to make sure a thread has finished (always gives 'true') +-- lane_h[-1]: error message, without propagating the error +-- +-- Reading a Lane result (or [0]) propagates a possible error in the lane +-- (and execution does not return). Cancelled lanes give 'nil' values. +-- +-- lane_h.state: "pending"/"running"/"waiting"/"done"/"error"/"cancelled" +-- +local lane_mt= { + __index= function( me, k ) + if type(k) == "number" then + -- 'me[0]=true' marks we've already taken in the results + -- + if not rawget( me, 0 ) then + -- Wait indefinately; either propagates an error or + -- returns the return values + -- + me[0]= true -- marker, even on errors + + local t= { thread_join(me._ud) } -- wait indefinate + -- + -- { ... } "done": regular return, 0..N results + -- { } "cancelled" + -- { nil, err_str, stack_tbl } "error" + + local st= thread_status(me._ud) + if st=="done" then + -- Use 'pairs' and not 'ipairs' so that nil holes in + -- the returned values are tolerated. + -- + for i,v in pairs(t) do + me[i]= v + end + elseif st=="error" then + assert( t[1]==nil and t[2] and type(t[3])=="table" ) + me[-1]= t[2] + -- me[-2] could carry the stack table, but even + -- me[-1] is rather unnecessary (and undocumented); + -- use ':join()' instead. --AKa 22-Jan-2009 + elseif st=="cancelled" then + -- do nothing + else + error( "Unexpected status: "..st ) + end + end + + -- Check errors even if we'd first peeked them via [-1] + -- and then came for the actual results. + -- + local err= rawget(me, -1) + if err~=nil and k~=-1 then + -- Note: Lua 5.1 interpreter is not prepared to show + -- non-string errors, so we use 'tostring()' here + -- to get meaningful output. --AKa 22-Jan-2009 + -- + -- Also, the stack dump we get is no good; it only + -- lists our internal Lanes functions. There seems + -- to be no way to switch it off, though. + + -- Level 3 should show the line where 'h[x]' was read + -- but this only seems to work for string messages + -- (Lua 5.1.4). No idea, why. --AKa 22-Jan-2009 + -- + error( tostring(err), 3 ) -- level 3 should show the line where 'h[x]' was read + end + return rawget( me, k ) + -- + elseif k=="status" then -- me.status + return thread_status(me._ud) + -- + else + error( "Unknown key: "..k ) + end + end + } + +----- +-- h= lanes.gen( [libs_str|opt_tbl [, ...],] lane_func ) ( [...] ) +-- +-- 'libs': nil: no libraries available (default) +-- "": only base library ('assert', 'print', 'unpack' etc.) +-- "math,os": math + os + base libraries (named ones + base) +-- "*": all standard libraries available +-- +-- 'opt': .priority: int (-2..+2) smaller is lower priority (0 = default) +-- +-- .cancelstep: bool | uint +-- false: cancellation check only at pending Linda operations +-- (send/receive) so no runtime performance penalty (default) +-- true: adequate cancellation check (same as 100) +-- >0: cancellation check every x Lua lines (small number= faster +-- reaction but more performance overhead) +-- +-- .globals: table of globals to set for a new thread (passed by value) +-- +-- ... (more options may be introduced later) ... +-- +-- Calling with a function parameter ('lane_func') ends the string/table +-- modifiers, and prepares a lane generator. One can either finish here, +-- and call the generator later (maybe multiple times, with different parameters) +-- or add on actual thread arguments to also ignite the thread on the same call. +-- +local lane_proxy + +local valid_libs= { + ["package"]= true, + ["table"]= true, + ["io"]= true, + ["os"]= true, + ["string"]= true, + ["math"]= true, + ["debug"]= true, + -- + ["base"]= true, + ["coroutine"]= true, + ["*"]= true +} + +function gen( ... ) + local opt= {} + local libs= nil + local lev= 2 -- level for errors + + local n= select('#',...) + + if n==0 then + error( "No parameters!" ) + end + + for i=1,n-1 do + local v= select(i,...) + if type(v)=="string" then + libs= libs and libs..","..v or v + elseif type(v)=="table" then + for k,vv in pairs(v) do + opt[k]= vv + end + elseif v==nil then + -- skip + else + error( "Bad parameter: "..tostring(v) ) + end + end + + local func= select(n,...) + if type(func)~="function" then + error( "Last parameter not function: "..tostring(func) ) + end + + -- Check 'libs' already here, so the error goes in the right place + -- (otherwise will be noticed only once the generator is called) + -- + if libs then + for s in string_gmatch(libs, "[%a*]+") do + if not valid_libs[s] then + error( "Bad library name: "..s ) + end + end + end + + local prio, cs, g_tbl + + for k,v in pairs(opt) do + if k=="priority" then prio= v + elseif k=="cancelstep" then cs= (v==true) and 100 or + (v==false) and 0 or + type(v)=="number" and v or + error( "Bad cancelstep: "..tostring(v), lev ) + elseif k=="globals" then g_tbl= v + --.. + elseif k==1 then error( "unkeyed option: ".. tostring(v), lev ) + else error( "Bad option: ".. tostring(k), lev ) + end + end + + -- Lane generator + -- + return function(...) + return lane_proxy( thread_new( func, libs, cs, prio, g_tbl, + ... ) ) -- args + end +end + +lane_proxy= function( ud ) + local proxy= { + _ud= ud, + + -- void= me:cancel() + -- + cancel= function(me) thread_cancel(me._ud) end, + + -- [...] | [nil,err,stack_tbl]= me:join( [wait_secs=-1] ) + -- + join= function( me, wait ) + return thread_join( me._ud, wait ) + end, + } + assert( proxy._ud ) + setmetatable( proxy, lane_mt ) + + return proxy +end + + +---=== Lindas ===--- + +-- We let the C code attach methods to userdata directly + +----- +-- linda_ud= lanes.linda() +-- +function linda() + local proxy= _deep_userdata( linda_id ) + assert( (type(proxy) == "userdata") and getmetatable(proxy) ) + return proxy +end + + +---=== Timers ===--- + +-- +-- On first 'require "lanes"', a timer lane is spawned that will maintain +-- timer tables and sleep in between the timer events. All interaction with +-- the timer lane happens via a 'timer_gateway' Linda, which is common to +-- all that 'require "lanes"'. +-- +-- Linda protocol to timer lane: +-- +-- TGW_KEY: linda_h, key, [wakeup_at_secs], [repeat_secs] +-- +local TGW_KEY= "(timer control)" -- the key does not matter, a 'weird' key may help debugging +local first_time_key= "first time" + +local first_time= timer_gateway:get(first_time_key) == nil +timer_gateway:set(first_time_key,true) + +-- +-- Timer lane; initialize only on the first 'require "lanes"' instance (which naturally +-- has 'table' always declared) +-- +if first_time then + local table_remove= assert( table.remove ) + local table_insert= assert( table.insert ) + + -- + -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, + -- [key]= { wakeup_secs [,period_secs] } [, ...] }, + -- } + -- + -- Collection of all running timers, indexed with linda's & key. + -- + -- Note that we need to use the deep lightuserdata identifiers, instead + -- of 'linda_h' themselves as table indices. Otherwise, we'd get multiple + -- entries for the same timer. + -- + -- The 'hidden' reference to Linda proxy is used in 'check_timers()' but + -- also important to keep the Linda alive, even if all outside world threw + -- away pointers to it (which would ruin uniqueness of the deep pointer). + -- Now we're safe. + -- + local collection= {} + + -- + -- set_timer( linda_h, key [,wakeup_at_secs [,period_secs]] ) + -- + local function set_timer( linda, key, wakeup_at, period ) + + assert( wakeup_at==nil or wakeup_at>0.0 ) + assert( period==nil or period>0.0 ) + + local linda_deep= linda:deep() + assert( linda_deep ) + + -- Find or make a lookup for this timer + -- + local t1= collection[linda_deep] + if not t1 then + t1= { [linda_deep]= linda } -- proxy to use the Linda + collection[linda_deep]= t1 + end + + if wakeup_at==nil then + -- Clear the timer + -- + t1[key]= nil + + -- Remove empty tables from collection; speeds timer checks and + -- lets our 'safety reference' proxy be gc:ed as well. + -- + local empty= true + for k,_ in pairs(t1) do + if k~= linda_deep then + empty= false; break + end + end + if empty then + collection[linda_deep]= nil + end + + -- Note: any unread timer value is left at 'linda[key]' intensionally; + -- clearing a timer just stops it. + else + -- New timer or changing the timings + -- + local t2= t1[key] + if not t2 then + t2= {}; t1[key]= t2 + end + + t2[1]= wakeup_at + t2[2]= period -- can be 'nil' + end + end + + ----- + -- [next_wakeup_at]= check_timers() + -- + -- Check timers, and wake up the ones expired (if any) + -- + -- Returns the closest upcoming (remaining) wakeup time (or 'nil' if none). + -- + local function check_timers() + + local now= now_secs() + local next_wakeup + + for linda_deep,t1 in pairs(collection) do + for key,t2 in pairs(t1) do + -- + if key==linda_deep then + -- no 'continue' in Lua :/ + else + -- 't2': { wakeup_at_secs [,period_secs] } + -- + local wakeup_at= t2[1] + local period= t2[2] -- may be 'nil' + + if wakeup_at <= now then + local linda= t1[linda_deep] + assert(linda) + + linda:set( key, now ) + + -- 'pairs()' allows the values to be modified (and even + -- removed) as far as keys are not touched + + if not period then + -- one-time timer; gone + -- + t1[key]= nil + wakeup_at= nil -- no 'continue' in Lua :/ + else + -- repeating timer; find next wakeup (may jump multiple repeats) + -- + repeat + wakeup_at= wakeup_at+period + until wakeup_at > now + + t2[1]= wakeup_at + end + end + + if wakeup_at and ((not next_wakeup) or (wakeup_at < next_wakeup)) then + next_wakeup= wakeup_at + end + end + end -- t2 loop + end -- t1 loop + + return next_wakeup -- may be 'nil' + end + + ----- + -- Snore loop (run as a lane on the background) + -- + -- High priority, to get trustworthy timings. + -- + -- We let the timer lane be a "free running" thread; no handle to it + -- remains. + -- + gen( "io", { priority=max_prio }, function() + + while true do + local next_wakeup= check_timers() + + -- Sleep until next timer to wake up, or a set/clear command + -- + local secs= next_wakeup and (next_wakeup - now_secs()) or nil + local linda= timer_gateway:receive( secs, TGW_KEY ) + + if linda then + local key= timer_gateway:receive( 0.0, TGW_KEY ) + local wakeup_at= timer_gateway:receive( 0.0, TGW_KEY ) + local period= timer_gateway:receive( 0.0, TGW_KEY ) + assert( key and wakeup_at and period ) + + set_timer( linda, key, wakeup_at, period>0 and period or nil ) + end + end + end )() +end + +----- +-- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) +-- +function timer( linda, key, a, period ) + + if a==0.0 then + -- Caller expects to get current time stamp in Linda, on return + -- (like the timer had expired instantly); it would be good to set this + -- as late as possible (to give most current time) but also we want it + -- to precede any possible timers that might start striking. + -- + linda:set( key, now_secs() ) + + if not period or period==0.0 then + timer_gateway:send( TGW_KEY, linda, key, nil, nil ) -- clear the timer + return -- nothing more to do + end + a= period + end + + local wakeup_at= type(a)=="table" and wakeup_conv(a) -- given point of time + or now_secs()+a + -- queue to timer + -- + timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) +end + + +---=== 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] ) +-- +-- = lock_f( +M ) -- acquire M +-- ...locked... +-- = lock_f( -M ) -- release M +-- +-- Returns an access function that allows 'N' simultaneous entries between +-- acquire (+M) and release (-M). For binary locks, use M==1. +-- +function genlock( 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 + end + + return + function(M) + if M>0 then + -- 'nil' timeout allows 'key' to be numeric + linda:send( nil, key, trues(M) ) -- suspends until been able to push them + else + for i=1,-M do + linda:receive( key ) + end + 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'. +-- +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) + + 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 + + +--the end diff --git a/src/lualanes/keeper.lch b/src/lualanes/keeper.lch new file mode 100644 index 0000000000000000000000000000000000000000..6d5f2fc482828dfce29998a05bbfc423878606bb --- /dev/null +++ b/src/lualanes/keeper.lch @@ -0,0 +1,214 @@ +/* bin2c.lua generated code -- DO NOT EDIT + * + * To use from C source: + * char my_chunk[]= + * #include "my.lch" + */ +{ + 27, 76,117, 97, 81, 0, 1, 4, 4, 4, 8, 0, 12, 0, 0, 0, 64,107,101,101, + 112,101,114, 46,108,117, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 9, + 45, 0, 0, 0, 5, 0, 0, 0, 69, 64, 0, 0, 28, 64, 0, 1, 5, 0, 0, 0, + 69,128, 0, 0, 70,192,192, 0, 28,128, 0, 1, 69, 0, 0, 0,133,128, 0, 0, + 134, 0, 65, 1, 92,128, 0, 1,164, 0, 0, 0, 0, 0,128, 0,202, 0, 0, 0, + 10, 1, 0, 0, 74, 1, 0, 0,164, 65, 0, 0, 0, 0,128, 1, 0, 0, 0, 2, + 0, 0,128, 2,228,129, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 36,194, 0, 0, + 0, 0, 0, 3, 7, 66, 1, 0, 36, 2, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, + 7,130, 1, 0, 36, 66, 1, 0, 0, 0, 0, 3, 7,194, 1, 0, 36,130, 1, 0, + 0, 0, 0, 3, 7, 2, 2, 0, 36,194, 1, 0, 0, 0, 0, 3, 7, 66, 2, 0, + 36, 2, 2, 0, 0, 0,128, 1, 0, 0, 0, 2, 0, 0,128, 2, 7,130, 2, 0, + 30, 0,128, 0, 11, 0, 0, 0, 4, 7, 0, 0, 0, 97,115,115,101,114,116, 0, + 4, 13, 0, 0, 0,110,105,108, 95,115,101,110,116,105,110,101,108, 0, 4, 6, + 0, 0, 0,116, 97, 98,108,101, 0, 4, 7, 0, 0, 0,114,101,109,111,118,101, + 0, 4, 7, 0, 0, 0, 99,111,110, 99, 97,116, 0, 4, 5, 0, 0, 0,115,101, + 110,100, 0, 4, 8, 0, 0, 0,114,101, 99,101,105,118,101, 0, 4, 6, 0, 0, + 0,108,105,109,105,116, 0, 4, 4, 0, 0, 0,115,101,116, 0, 4, 4, 0, 0, + 0,103,101,116, 0, 4, 6, 0, 0, 0, 99,108,101, 97,114, 0, 9, 0, 0, 0, + 0, 0, 0, 0, 46, 0, 0, 0, 50, 0, 0, 0, 1, 0, 3, 6, 16, 0, 0, 0, + 69, 0, 0, 0, 90, 0, 0, 0, 22,192, 2,128, 69, 0, 0, 0, 70, 64,192, 0, + 75,128,192, 0,196, 0, 0, 0, 10, 1, 0, 0,101, 1, 0, 0, 34, 65, 0, 0, + 65,193, 0, 0,220,128,128, 1, 1, 1, 1, 0,213, 0,129, 1, 92, 64,128, 1, + 30, 0,128, 0, 5, 0, 0, 0, 4, 3, 0, 0, 0,105,111, 0, 4, 7, 0, 0, + 0,115,116,100,101,114,114, 0, 4, 6, 0, 0, 0,119,114,105,116,101, 0, 4, + 2, 0, 0, 0, 9, 0, 4, 2, 0, 0, 0, 10, 0, 0, 0, 0, 0, 16, 0, 0, + 0, 47, 0, 0, 0, 47, 0, 0, 0, 47, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, + 0, 48, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, + 0, 48, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, 0, 48, 0, 0, + 0, 50, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 97,114,103, 0, 0, 0, 0, + 0, 15, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0,116, 97, 98,108,101, 95, 99, + 111,110, 99, 97,116, 0, 0, 0, 0, 0, 84, 0, 0, 0, 93, 0, 0, 0, 3, 1, + 0, 4, 21, 0, 0, 0, 68, 0, 0, 0, 70, 0,128, 0, 90, 64, 0, 0, 22, 0, + 2,128, 68, 0, 0, 0,138, 0, 0, 0, 73,128, 0, 0, 68, 0,128, 0,138, 0, + 0, 0, 73,128, 0, 0, 68, 0, 0, 1,138, 0, 0, 0, 73,128, 0, 0, 68, 0, + 0, 0, 70, 0,128, 0,132, 0,128, 0,134, 0, 0, 1,196, 0, 0, 1,198, 0, + 128, 1, 94, 0, 0, 2, 30, 0,128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21, 0, + 0, 0, 87, 0, 0, 0, 87, 0, 0, 0, 87, 0, 0, 0, 87, 0, 0, 0, 88, 0, + 0, 0, 88, 0, 0, 0, 88, 0, 0, 0, 89, 0, 0, 0, 89, 0, 0, 0, 89, 0, + 0, 0, 90, 0, 0, 0, 90, 0, 0, 0, 90, 0, 0, 0, 92, 0, 0, 0, 92, 0, + 0, 0, 92, 0, 0, 0, 92, 0, 0, 0, 92, 0, 0, 0, 92, 0, 0, 0, 92, 0, + 0, 0, 93, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0,117,100, 0, 0, 0, 0, + 0, 20, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 95,100, 97,116, 97, 0, 10, + 0, 0, 0, 95,105,110, 99,111,109,105,110,103, 0, 8, 0, 0, 0, 95,108,105, + 109,105,116,115, 0, 0, 0, 0, 0, 96, 0, 0, 0,106, 0, 0, 0, 2, 3, 0, + 16, 40, 0, 0, 0,197, 0, 0, 0, 27, 1, 0, 0, 22,128, 0,128, 27, 1,128, + 0, 22, 0, 0,128, 0, 1, 0, 1,220, 64, 0, 1,196, 0, 0, 0, 0, 1,128, + 0,220, 0, 1, 1,133, 65, 0, 0,198,129,128, 1,156,129, 0, 1,197,129, 0, + 0, 6,130, 0, 2, 26, 66, 0, 0, 22, 0, 0,128, 10, 2, 0, 0,220, 1, 1, + 1, 22, 64, 1,128, 0, 3, 0, 3, 65,195, 0, 0,133, 67, 0, 0,192, 3,128, + 5,156,131, 0, 1,149,129, 3, 6,225,129, 0, 0, 22,192,253,127,196, 1,128, + 0, 1, 2, 1, 0, 64, 2, 0, 0,129, 66, 1, 0,197, 66, 0, 0, 0, 3, 0, + 1,220,130, 0, 1, 1,131, 1, 0, 21, 2, 3, 4, 64, 2, 0, 3,220, 65,128, + 1, 30, 0,128, 0, 7, 0, 0, 0, 4, 7, 0, 0, 0, 97,115,115,101,114,116, + 0, 4, 9, 0, 0, 0,116,111,115,116,114,105,110,103, 0, 4, 7, 0, 0, 0, + 105,112, 97,105,114,115, 0, 4, 3, 0, 0, 0, 44, 32, 0, 4, 5, 0, 0, 0, + 42, 42, 42, 32, 0, 4, 3, 0, 0, 0, 32, 40, 0, 4, 4, 0, 0, 0, 41, 58, + 32, 0, 0, 0, 0, 0, 40, 0, 0, 0, 97, 0, 0, 0, 97, 0, 0, 0, 97, 0, + 0, 0, 97, 0, 0, 0, 97, 0, 0, 0, 97, 0, 0, 0, 97, 0, 0, 0, 99, 0, + 0, 0, 99, 0, 0, 0, 99, 0, 0, 0,101, 0, 0, 0,101, 0, 0, 0,101, 0, + 0, 0,102, 0, 0, 0,102, 0, 0, 0,102, 0, 0, 0,102, 0, 0, 0,102, 0, + 0, 0,102, 0, 0, 0,102, 0, 0, 0,103, 0, 0, 0,103, 0, 0, 0,103, 0, + 0, 0,103, 0, 0, 0,103, 0, 0, 0,103, 0, 0, 0,102, 0, 0, 0,103, 0, + 0, 0,105, 0, 0, 0,105, 0, 0, 0,105, 0, 0, 0,105, 0, 0, 0,105, 0, + 0, 0,105, 0, 0, 0,105, 0, 0, 0,105, 0, 0, 0,105, 0, 0, 0,105, 0, + 0, 0,105, 0, 0, 0,106, 0, 0, 0, 12, 0, 0, 0, 6, 0, 0, 0,116,105, + 116,108,101, 0, 0, 0, 0, 0, 39, 0, 0, 0, 3, 0, 0, 0,117,100, 0, 0, + 0, 0, 0, 39, 0, 0, 0, 4, 0, 0, 0,107,101,121, 0, 0, 0, 0, 0, 39, + 0, 0, 0, 5, 0, 0, 0,100, 97,116, 97, 0, 10, 0, 0, 0, 39, 0, 0, 0, + 9, 0, 0, 0,105,110, 99,111,109,105,110,103, 0, 10, 0, 0, 0, 39, 0, 0, + 0, 2, 0, 0, 0, 95, 0, 10, 0, 0, 0, 39, 0, 0, 0, 2, 0, 0, 0,115, + 0, 13, 0, 0, 0, 39, 0, 0, 0, 16, 0, 0, 0, 40,102,111,114, 32,103,101, + 110,101,114, 97,116,111,114, 41, 0, 19, 0, 0, 0, 28, 0, 0, 0, 12, 0, 0, + 0, 40,102,111,114, 32,115,116, 97,116,101, 41, 0, 19, 0, 0, 0, 28, 0, 0, + 0, 14, 0, 0, 0, 40,102,111,114, 32, 99,111,110,116,114,111,108, 41, 0, 19, + 0, 0, 0, 28, 0, 0, 0, 2, 0, 0, 0, 95, 0, 20, 0, 0, 0, 26, 0, 0, + 0, 2, 0, 0, 0,118, 0, 20, 0, 0, 0, 26, 0, 0, 0, 2, 0, 0, 0, 7, + 0, 0, 0,116, 97, 98,108,101,115, 0, 3, 0, 0, 0, 87, 82, 0, 0, 0, 0, + 0,121, 0, 0, 0,158, 0, 0, 0, 1, 2, 3, 16, 56, 0, 0, 0,196, 0, 0, + 0, 0, 1, 0, 0,220, 0, 1, 1,133, 1, 0, 0,193, 65, 0, 0, 37, 2, 0, + 0,156,129, 0, 0, 23,128, 64, 3, 22, 64, 0,128,194, 1,128, 0,222, 1, 0, + 1,198, 65, 0, 2, 23,192,192, 3, 22, 64, 0,128,202, 1, 0, 0, 9,193,129, + 0,198, 65,128, 1,218, 1, 0, 0, 22, 0, 1,128,198, 65, 0, 2,212, 1,128, + 3,204,193, 1,130,218, 65, 0, 0, 22, 0, 0,128,193,129, 0, 0, 6, 66,128, + 2, 26, 2, 0, 0, 22, 0, 1,128, 76,130,129, 3, 24, 64, 2, 4, 22, 64, 0, + 128, 66, 2, 0, 0, 94, 2, 0, 1, 65, 2, 1, 0,128, 2, 0, 3,193, 2, 1, + 0, 96,130, 3,128, 69, 3, 0, 0,128, 3, 0, 6,229, 3, 0, 0, 92,131, 0, + 0, 23,192,192, 6, 22, 0, 0,128, 69, 67, 1, 0, 23,128,192, 3, 22,128, 0, + 128,201, 64,131, 0,193, 1, 1, 0, 22,128, 0,128,134, 67, 0, 2,137, 67,131, + 3,204, 1,193, 3, 95,194,251,127, 66, 2,128, 0, 94, 2, 0, 1, 30, 0,128, + 0, 6, 0, 0, 0, 4, 7, 0, 0, 0,115,101,108,101, 99,116, 0, 4, 2, 0, + 0, 0, 35, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0,240, 63, 4, 13, 0, 0, 0,110,105,108, 95,115,101,110,116,105,110,101,108, + 0, 0, 0, 0, 0, 56, 0, 0, 0,123, 0, 0, 0,123, 0, 0, 0,123, 0, 0, + 0,125, 0, 0, 0,125, 0, 0, 0,125, 0, 0, 0,125, 0, 0, 0,126, 0, 0, + 0,126, 0, 0, 0,126, 0, 0, 0,126, 0, 0, 0,130, 0, 0, 0,130, 0, 0, + 0,130, 0, 0, 0,131, 0, 0, 0,131, 0, 0, 0,134, 0, 0, 0,134, 0, 0, + 0,134, 0, 0, 0,134, 0, 0, 0,134, 0, 0, 0,134, 0, 0, 0,134, 0, 0, + 0,134, 0, 0, 0,134, 0, 0, 0,135, 0, 0, 0,137, 0, 0, 0,137, 0, 0, + 0,137, 0, 0, 0,137, 0, 0, 0,137, 0, 0, 0,138, 0, 0, 0,138, 0, 0, + 0,141, 0, 0, 0,141, 0, 0, 0,141, 0, 0, 0,141, 0, 0, 0,142, 0, 0, + 0,142, 0, 0, 0,142, 0, 0, 0,142, 0, 0, 0,145, 0, 0, 0,145, 0, 0, + 0,146, 0, 0, 0,149, 0, 0, 0,149, 0, 0, 0,150, 0, 0, 0,151, 0, 0, + 0,151, 0, 0, 0,153, 0, 0, 0,153, 0, 0, 0,154, 0, 0, 0,141, 0, 0, + 0,157, 0, 0, 0,157, 0, 0, 0,158, 0, 0, 0, 14, 0, 0, 0, 3, 0, 0, + 0,117,100, 0, 0, 0, 0, 0, 55, 0, 0, 0, 4, 0, 0, 0,107,101,121, 0, + 0, 0, 0, 0, 55, 0, 0, 0, 4, 0, 0, 0, 97,114,103, 0, 0, 0, 0, 0, + 55, 0, 0, 0, 5, 0, 0, 0,100, 97,116, 97, 0, 3, 0, 0, 0, 55, 0, 0, + 0, 9, 0, 0, 0,105,110, 99,111,109,105,110,103, 0, 3, 0, 0, 0, 55, 0, + 0, 0, 7, 0, 0, 0,108,105,109,105,116,115, 0, 3, 0, 0, 0, 55, 0, 0, + 0, 2, 0, 0, 0,110, 0, 7, 0, 0, 0, 55, 0, 0, 0, 4, 0, 0, 0,108, + 101,110, 0, 25, 0, 0, 0, 55, 0, 0, 0, 2, 0, 0, 0,109, 0, 26, 0, 0, + 0, 55, 0, 0, 0, 12, 0, 0, 0, 40,102,111,114, 32,105,110,100,101,120, 41, + 0, 36, 0, 0, 0, 53, 0, 0, 0, 12, 0, 0, 0, 40,102,111,114, 32,108,105, + 109,105,116, 41, 0, 36, 0, 0, 0, 53, 0, 0, 0, 11, 0, 0, 0, 40,102,111, + 114, 32,115,116,101,112, 41, 0, 36, 0, 0, 0, 53, 0, 0, 0, 2, 0, 0, 0, + 105, 0, 37, 0, 0, 0, 52, 0, 0, 0, 4, 0, 0, 0,118, 97,108, 0, 41, 0, + 0, 0, 52, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0,116, 97, 98,108,101,115, + 0, 0, 0, 0, 0,167, 0, 0, 0,189, 0, 0, 0, 2, 1, 3, 14, 40, 0, 0, + 0,132, 0, 0, 0,192, 0, 0, 0,156, 0, 1, 1, 65, 1, 0, 0,133, 65, 0, + 0,193,129, 0, 0, 37, 2, 0, 0,156,129, 0, 0,193, 1, 0, 0, 96,193, 6, + 128, 69, 66, 0, 0,128, 2, 0, 4,229, 2, 0, 0, 92,130, 0, 0,134, 66, 2, + 1, 87,192, 64, 5, 22, 0, 5,128,198, 66,130, 1,218, 2, 0, 0, 22, 64, 2, + 128,198, 66,130, 1,198, 2,192, 5, 87,192,192, 5, 22, 64, 1,128,196, 2,128, + 0, 6, 67,130, 1, 65, 3, 0, 0,220,130,128, 1,137,192,130, 4, 22, 0, 0, + 128,137,192,192, 4,197, 2, 1, 0, 23,192, 2, 5, 22, 0, 0,128,131, 2, 0, + 5,192, 2, 0, 5, 0, 3,128, 4,222, 2,128, 1, 95,129,248,127, 30, 0,128, + 0, 5, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0,240, 63, 4, 7, 0, 0, 0,115, + 101,108,101, 99,116, 0, 4, 2, 0, 0, 0, 35, 0, 0, 4, 13, 0, 0, 0,110, + 105,108, 95,115,101,110,116,105,110,101,108, 0, 0, 0, 0, 0, 40, 0, 0, 0, + 169, 0, 0, 0,169, 0, 0, 0,169, 0, 0, 0,171, 0, 0, 0,171, 0, 0, 0, + 171, 0, 0, 0,171, 0, 0, 0,171, 0, 0, 0,171, 0, 0, 0,171, 0, 0, 0, + 172, 0, 0, 0,172, 0, 0, 0,172, 0, 0, 0,172, 0, 0, 0,173, 0, 0, 0, + 175, 0, 0, 0,175, 0, 0, 0,176, 0, 0, 0,176, 0, 0, 0,176, 0, 0, 0, + 176, 0, 0, 0,176, 0, 0, 0,176, 0, 0, 0,176, 0, 0, 0,178, 0, 0, 0, + 178, 0, 0, 0,178, 0, 0, 0,178, 0, 0, 0,178, 0, 0, 0,178, 0, 0, 0, + 180, 0, 0, 0,182, 0, 0, 0,182, 0, 0, 0,182, 0, 0, 0,183, 0, 0, 0, + 185, 0, 0, 0,185, 0, 0, 0,185, 0, 0, 0,171, 0, 0, 0,189, 0, 0, 0, + 11, 0, 0, 0, 3, 0, 0, 0,117,100, 0, 0, 0, 0, 0, 39, 0, 0, 0, 4, + 0, 0, 0, 97,114,103, 0, 0, 0, 0, 0, 39, 0, 0, 0, 5, 0, 0, 0,100, + 97,116, 97, 0, 3, 0, 0, 0, 39, 0, 0, 0, 9, 0, 0, 0,105,110, 99,111, + 109,105,110,103, 0, 3, 0, 0, 0, 39, 0, 0, 0, 2, 0, 0, 0, 95, 0, 3, + 0, 0, 0, 39, 0, 0, 0, 12, 0, 0, 0, 40,102,111,114, 32,105,110,100,101, + 120, 41, 0, 9, 0, 0, 0, 39, 0, 0, 0, 12, 0, 0, 0, 40,102,111,114, 32, + 108,105,109,105,116, 41, 0, 9, 0, 0, 0, 39, 0, 0, 0, 11, 0, 0, 0, 40, + 102,111,114, 32,115,116,101,112, 41, 0, 9, 0, 0, 0, 39, 0, 0, 0, 2, 0, + 0, 0,105, 0, 10, 0, 0, 0, 38, 0, 0, 0, 4, 0, 0, 0,107,101,121, 0, + 14, 0, 0, 0, 38, 0, 0, 0, 4, 0, 0, 0,118, 97,108, 0, 15, 0, 0, 0, + 38, 0, 0, 0, 2, 0, 0, 0, 7, 0, 0, 0,116, 97, 98,108,101,115, 0, 13, + 0, 0, 0,116, 97, 98,108,101, 95,114,101,109,111,118,101, 0, 0, 0, 0, 0, + 195, 0, 0, 0,200, 0, 0, 0, 1, 3, 0, 6, 5, 0, 0, 0,196, 0, 0, 0, + 0, 1, 0, 0,220, 0, 1, 1, 73,129,128, 0, 30, 0,128, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 5, 0, 0, 0,197, 0, 0, 0,197, 0, 0, 0,197, 0, 0, 0, + 199, 0, 0, 0,200, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0,117,100, 0, 0, + 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0,107,101,121, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 2, 0, 0, 0,110, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, + 0, 95, 0, 3, 0, 0, 0, 4, 0, 0, 0, 2, 0, 0, 0, 95, 0, 3, 0, 0, + 0, 4, 0, 0, 0, 7, 0, 0, 0,108,105,109,105,116,115, 0, 3, 0, 0, 0, + 4, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0,116, 97, 98,108,101,115, 0, 0, + 0, 0, 0,206, 0, 0, 0,214, 0, 0, 0, 1, 3, 0, 6, 6, 0, 0, 0,196, + 0, 0, 0, 0, 1, 0, 0,220, 0, 1, 1,201,128,128, 0, 9, 1,192, 0, 30, + 0,128, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0,208, 0, 0, 0, + 208, 0, 0, 0,208, 0, 0, 0,212, 0, 0, 0,213, 0, 0, 0,214, 0, 0, 0, + 6, 0, 0, 0, 3, 0, 0, 0,117,100, 0, 0, 0, 0, 0, 5, 0, 0, 0, 4, + 0, 0, 0,107,101,121, 0, 0, 0, 0, 0, 5, 0, 0, 0, 4, 0, 0, 0,118, + 97,108, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0,100, 97,116, 97, 0, + 3, 0, 0, 0, 5, 0, 0, 0, 9, 0, 0, 0,105,110, 99,111,109,105,110,103, + 0, 3, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 95, 0, 3, 0, 0, 0, 5, + 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0,116, 97, 98,108,101,115, 0, 0, 0, + 0, 0,220, 0, 0, 0,229, 0, 0, 0, 1, 2, 0, 7, 10, 0, 0, 0,132, 0, + 0, 0,192, 0, 0, 0,156, 0, 1, 1, 70, 65, 0, 1,133, 1, 0, 0, 23,128, + 129, 2, 22, 0, 0,128, 67, 1,128, 2, 94, 1, 0, 1, 30, 0,128, 0, 1, 0, + 0, 0, 4, 13, 0, 0, 0,110,105,108, 95,115,101,110,116,105,110,101,108, 0, + 0, 0, 0, 0, 10, 0, 0, 0,222, 0, 0, 0,222, 0, 0, 0,222, 0, 0, 0, + 224, 0, 0, 0,225, 0, 0, 0,225, 0, 0, 0,225, 0, 0, 0,226, 0, 0, 0, + 228, 0, 0, 0,229, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0,117,100, 0, 0, + 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0,107,101,121, 0, 0, 0, 0, 0, 9, + 0, 0, 0, 5, 0, 0, 0,100, 97,116, 97, 0, 3, 0, 0, 0, 9, 0, 0, 0, + 2, 0, 0, 0, 95, 0, 3, 0, 0, 0, 9, 0, 0, 0, 2, 0, 0, 0, 95, 0, + 3, 0, 0, 0, 9, 0, 0, 0, 4, 0, 0, 0,118, 97,108, 0, 4, 0, 0, 0, + 9, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0,116, 97, 98,108,101,115, 0, 0, + 0, 0, 0,237, 0, 0, 0,242, 0, 0, 0, 3, 1, 0, 2, 7, 0, 0, 0, 68, + 0, 0, 0, 73, 0, 64, 0, 68, 0,128, 0, 73, 0, 64, 0, 68, 0, 0, 1, 73, + 0, 64, 0, 30, 0,128, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, + 239, 0, 0, 0,239, 0, 0, 0,240, 0, 0, 0,240, 0, 0, 0,241, 0, 0, 0, + 241, 0, 0, 0,242, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0,117,100, 0, 0, + 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 95,100, 97,116, 97, + 0, 10, 0, 0, 0, 95,105,110, 99,111,109,105,110,103, 0, 8, 0, 0, 0, 95, + 108,105,109,105,116,115, 0, 45, 0, 0, 0, 39, 0, 0, 0, 39, 0, 0, 0, 39, + 0, 0, 0, 43, 0, 0, 0, 43, 0, 0, 0, 43, 0, 0, 0, 43, 0, 0, 0, 44, + 0, 0, 0, 44, 0, 0, 0, 44, 0, 0, 0, 44, 0, 0, 0, 50, 0, 0, 0, 50, + 0, 0, 0, 59, 0, 0, 0, 68, 0, 0, 0, 77, 0, 0, 0, 93, 0, 0, 0, 93, + 0, 0, 0, 93, 0, 0, 0, 93, 0, 0, 0,106, 0, 0, 0,106, 0, 0, 0,106, + 0, 0, 0,158, 0, 0, 0,158, 0, 0, 0,121, 0, 0, 0,189, 0, 0, 0,189, + 0, 0, 0,189, 0, 0, 0,167, 0, 0, 0,200, 0, 0, 0,200, 0, 0, 0,195, + 0, 0, 0,214, 0, 0, 0,214, 0, 0, 0,206, 0, 0, 0,229, 0, 0, 0,229, + 0, 0, 0,220, 0, 0, 0,242, 0, 0, 0,242, 0, 0, 0,242, 0, 0, 0,242, + 0, 0, 0,237, 0, 0, 0,242, 0, 0, 0, 8, 0, 0, 0, 13, 0, 0, 0,116, + 97, 98,108,101, 95,114,101,109,111,118,101, 0, 7, 0, 0, 0, 44, 0, 0, 0, + 13, 0, 0, 0,116, 97, 98,108,101, 95, 99,111,110, 99, 97,116, 0, 11, 0, 0, + 0, 44, 0, 0, 0, 3, 0, 0, 0, 87, 82, 0, 13, 0, 0, 0, 44, 0, 0, 0, + 6, 0, 0, 0, 95,100, 97,116, 97, 0, 14, 0, 0, 0, 44, 0, 0, 0, 10, 0, + 0, 0, 95,105,110, 99,111,109,105,110,103, 0, 15, 0, 0, 0, 44, 0, 0, 0, + 8, 0, 0, 0, 95,108,105,109,105,116,115, 0, 16, 0, 0, 0, 44, 0, 0, 0, + 7, 0, 0, 0,116, 97, 98,108,101,115, 0, 20, 0, 0, 0, 44, 0, 0, 0, 6, + 0, 0, 0, 68, 69, 66, 85, 71, 0, 23, 0, 0, 0, 44, 0, 0, 0, 0, 0, 0, + 0,}; + diff --git a/src/lualanes/lanes.c b/src/lualanes/lanes.c new file mode 100644 index 0000000000000000000000000000000000000000..9b36e4d0c7a650e3d51f265be3068d2085d3c18f --- /dev/null +++ b/src/lualanes/lanes.c @@ -0,0 +1,1849 @@ +/* + * LANES.C Copyright (c) 2007-08, Asko Kauppi + * + * Multithreading in Lua. + * + * History: + * 20-Oct-08 (2.0.2): Added closing of free-running threads, but it does + * not seem to eliminate the occasional segfaults at process + * exit. + * ... + * 24-Jun-08 .. 14-Aug-08 AKa: Major revise, Lanes 2008 version (2.0 rc1) + * ... + * 18-Sep-06 AKa: Started the module. + * + * Platforms (tested internally): + * OS X (10.5.4 PowerPC/Intel) + * Linux x86 (Ubuntu 8.04) + * Win32 (Windows XP Home SP2, Visual C++ 2005/2008 Express) + * PocketPC (TBD) + * + * Platforms (tested externally): + * Win32 (MSYS) by Ross Berteig. + * + * Platforms (testers appreciated): + * Win64 - should work??? + * Linux x64 - should work + * FreeBSD - should work + * QNX - porting shouldn't be hard + * Sun Solaris - porting shouldn't be hard + * + * References: + * "Porting multithreaded applications from Win32 to Mac OS X": + * <http://developer.apple.com/macosx/multithreadedprogramming.html> + * + * Pthreads: + * <http://vergil.chemistry.gatech.edu/resources/programming/threads.html> + * + * MSDN: <http://msdn2.microsoft.com/en-us/library/ms686679.aspx> + * + * <http://ridiculousfish.com/blog/archives/2007/02/17/barrier> + * + * Defines: + * -DLINUX_SCHED_RR: all threads are lifted to SCHED_RR category, to + * allow negative priorities (-2,-1) be used. Even without this, + * using priorities will require 'sudo' privileges on Linux. + * + * -DUSE_PTHREAD_TIMEDJOIN: use 'pthread_timedjoin_np()' for waiting + * for threads with a timeout. This changes the thread cleanup + * mechanism slightly (cleans up at the join, not once the thread + * has finished). May or may not be a good idea to use it. + * Available only in selected operating systems (Linux). + * + * Bugs: + * + * To-do: + * + * ... + */ + +const char *VERSION= "2.0.3"; + +/* +=============================================================================== + +Copyright (C) 2007-08 Asko Kauppi <akauppi@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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=============================================================================== +*/ +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> + +#include "lua.h" +#include "lauxlib.h" + +#include "threading.h" +#include "tools.h" + +#if !((defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)) +# include <sys/time.h> +#endif + +/* geteuid() */ +#ifdef PLATFORM_LINUX +# include <unistd.h> +# include <sys/types.h> +#endif + +/* The selected number is not optimal; needs to be tested. Even using just +* one keeper state may be good enough (depends on the number of Lindas used +* in the applications). +*/ +#define KEEPER_STATES_N 1 // 6 + +/* Do you want full call stacks, or just the line where the error happened? +* +* TBD: The full stack feature does not seem to work (try 'make error'). +*/ +#define ERROR_FULL_STACK + +#ifdef ERROR_FULL_STACK +# define STACK_TRACE_KEY ((void*)lane_error) // used as registry key +#endif + +/* +* Lua code for the keeper states (baked in) +*/ +static char keeper_chunk[]= +#include "keeper.lch" + +struct s_lane; +static bool_t cancel_test( lua_State *L ); +static void cancel_error( lua_State *L ); + +#define CANCEL_TEST_KEY ((void*)cancel_test) // used as registry key +#define CANCEL_ERROR ((void*)cancel_error) // 'cancel_error' sentinel + +/* +* registry[FINALIZER_REG_KEY] is either nil (no finalizers) or a table +* of functions that Lanes will call after the executing 'pcall' has ended. +* +* We're NOT using the GC system for finalizer mainly because providing the +* error (and maybe stack trace) parameters to the finalizer functions would +* anyways complicate that approach. +*/ +#define FINALIZER_REG_KEY ((void*)LG_set_finalizer) + +struct s_Linda; + +#if 1 +# define DEBUG_SIGNAL( msg, signal_ref ) /* */ +#else +# define DEBUG_SIGNAL( msg, signal_ref ) \ + { int i; unsigned char *ptr; char buf[999]; \ + sprintf( buf, ">>> " msg ": %p\t", (signal_ref) ); \ + ptr= (unsigned char *)signal_ref; \ + for( i=0; i<sizeof(*signal_ref); i++ ) { \ + sprintf( strchr(buf,'\0'), "%02x %c ", ptr[i], ptr[i] ); \ + } \ + fprintf( stderr, "%s\n", buf ); \ + } +#endif + +static bool_t thread_cancel( struct s_lane *s, double secs, bool_t force ); + + +/* +* Push a table stored in registry onto Lua stack. +* +* If there is no existing table, create one if 'create' is TRUE. +* +* Returns: TRUE if a table was pushed +* FALSE if no table found, not created, and nothing pushed +*/ +static bool_t push_registry_table( lua_State *L, void *key, bool_t create ) { + + STACK_GROW(L,3); + + lua_pushlightuserdata( L, key ); + lua_gettable( L, LUA_REGISTRYINDEX ); + + if (lua_isnil(L,-1)) { + lua_pop(L,1); + + if (!create) return FALSE; // nothing pushed + + lua_newtable(L); + lua_pushlightuserdata( L, key ); + lua_pushvalue(L,-2); // duplicate of the table + lua_settable( L, LUA_REGISTRYINDEX ); + + // [-1]: table that's also bound in registry + } + return TRUE; // table pushed +} + + +/*---=== Serialize require ===--- +*/ + +static MUTEX_T require_cs; + +//--- +// [val]= new_require( ... ) +// +// Call 'old_require' but only one lane at a time. +// +// Upvalues: [1]: original 'require' function +// +static int new_require( lua_State *L ) { + int rc; + int args= lua_gettop(L); + + STACK_GROW(L,1); + STACK_CHECK(L) + + // Using 'lua_pcall()' to catch errors; otherwise a failing 'require' would + // leave us locked, blocking any future 'require' calls from other lanes. + // + MUTEX_LOCK( &require_cs ); + { + lua_pushvalue( L, lua_upvalueindex(1) ); + lua_insert( L, 1 ); + + rc= lua_pcall( L, args, 1 /*retvals*/, 0 /*errfunc*/ ); + // + // LUA_ERRRUN / LUA_ERRMEM + } + MUTEX_UNLOCK( &require_cs ); + + if (rc) lua_error(L); // error message already at [-1] + + STACK_END(L,0) + return 1; +} + +/* +* Serialize calls to 'require', if it exists +*/ +static +void serialize_require( lua_State *L ) { + + STACK_GROW(L,1); + STACK_CHECK(L) + + // Check 'require' is there; if not, do nothing + // + lua_getglobal( L, "require" ); + if (lua_isfunction( L, -1 )) { + // [-1]: original 'require' function + + lua_pushcclosure( L, new_require, 1 /*upvalues*/ ); + lua_setglobal( L, "require" ); + + } else { + // [-1]: nil + lua_pop(L,1); + } + + STACK_END(L,0) +} + + +/*---=== Keeper states ===--- +*/ + +/* +* Pool of keeper states +* +* Access to keeper states is locked (only one OS thread at a time) so the +* bigger the pool, the less chances of unnecessary waits. Lindas map to the +* keepers randomly, by a hash. +*/ +struct s_Keeper { + MUTEX_T lock_; + lua_State *L; +} keeper[ KEEPER_STATES_N ]; + +/* We could use an empty table in 'keeper.lua' as the sentinel, but maybe +* checking for a lightuserdata is faster. +*/ +static bool_t nil_sentinel; + +/* +* 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. +*/ +static const char *init_keepers(void) { + unsigned int i; + for( i=0; i<KEEPER_STATES_N; i++ ) { + + // Initialize Keeper states with bare minimum of libs (those required + // by 'keeper.lua') + // + lua_State *L= luaL_newstate(); + if (!L) return "out of memory"; + + luaG_openlibs( L, "io,table" ); // 'io' for debugging messages + + lua_pushlightuserdata( L, &nil_sentinel ); + lua_setglobal( L, "nil_sentinel" ); + + // Read in the preloaded chunk (and run it) + // + if (luaL_loadbuffer( L, keeper_chunk, sizeof(keeper_chunk), "=lanes_keeper" )) + return "luaL_loadbuffer() failed"; // LUA_ERRMEM + + if (lua_pcall( L, 0 /*args*/, 0 /*results*/, 0 /*errfunc*/ )) { + // LUA_ERRRUN / LUA_ERRMEM / LUA_ERRERR + // + const char *err= lua_tostring(L,-1); + assert(err); + return err; + } + + MUTEX_INIT( &keeper[i].lock_ ); + keeper[i].L= L; + } + return NULL; // ok +} + +static +struct s_Keeper *keeper_acquire( const void *ptr ) { + /* + * Any hashing will do that maps pointers to 0..KEEPER_STATES_N-1 + * consistently. + * + * Pointers are often aligned by 8 or so - ignore the low order bits + */ + unsigned int i= ((unsigned long)(ptr) >> 3) % KEEPER_STATES_N; + struct s_Keeper *K= &keeper[i]; + + MUTEX_LOCK( &K->lock_ ); + return K; +} + +static +void keeper_release( struct s_Keeper *K ) { + MUTEX_UNLOCK( &K->lock_ ); +} + +/* +* Call a function ('func_name') in the keeper state, and pass on the returned +* values to 'L'. +* +* 'linda': deep Linda pointer (used only as a unique table key, first parameter) +* 'starting_index': first of the rest of parameters (none if 0) +* +* Returns: number of return values (pushed to 'L') +*/ +static +int keeper_call( lua_State* K, const char *func_name, + lua_State *L, struct s_Linda *linda, uint_t starting_index ) { + + int args= starting_index ? (lua_gettop(L) - starting_index +1) : 0; + int Ktos= lua_gettop(K); + int retvals; + + lua_getglobal( K, func_name ); + ASSERT_L( lua_isfunction(K,-1) ); + + STACK_GROW( K, 1 ); + lua_pushlightuserdata( K, linda ); + + luaG_inter_copy( L,K, args ); // L->K + lua_call( K, 1+args, LUA_MULTRET ); + + retvals= lua_gettop(K) - Ktos; + + luaG_inter_move( K,L, retvals ); // K->L + return retvals; +} + + +/*---=== Linda ===--- +*/ + +/* +* Actual data is kept within a keeper state, which is hashed by the 's_Linda' +* pointer (which is same to all userdatas pointing to it). +*/ +struct s_Linda { + SIGNAL_T read_happened; + SIGNAL_T write_happened; +}; + +static int LG_linda_id( lua_State* ); + +#define lua_toLinda(L,n) ((struct s_Linda *)luaG_todeep( L, LG_linda_id, n )) + + +/* +* bool= linda_send( linda_ud, [timeout_secs=-1,] key_num|str|bool|lightuserdata, ... ) +* +* Send one or more values to a Linda. If there is a limit, all values must fit. +* +* Returns: 'true' if the value was queued +* 'false' for timeout (only happens when the queue size is limited) +*/ +LUAG_FUNC( linda_send ) { + struct s_Linda *linda= lua_toLinda( L, 1 ); + bool_t ret; + bool_t cancel= FALSE; + struct s_Keeper *K; + time_d timeout= -1.0; + uint_t key_i= 2; // index of first key, if timeout not there + + if (lua_isnumber(L,2)) { + timeout= SIGNAL_TIMEOUT_PREPARE( lua_tonumber(L,2) ); + key_i++; + } else if (lua_isnil(L,2)) + key_i++; + + if (lua_isnil(L,key_i)) + luaL_error( L, "nil key" ); + + STACK_GROW(L,1); + + K= keeper_acquire( linda ); + { + lua_State *KL= K->L; // need to do this for 'STACK_CHECK' +STACK_CHECK(KL) + while(TRUE) { + int pushed; + +STACK_MID(KL,0) + pushed= keeper_call( K->L, "send", L, linda, key_i ); + ASSERT_L( pushed==1 ); + + ret= lua_toboolean(L,-1); + lua_pop(L,1); + + if (ret) { + // Wake up ALL waiting threads + // + SIGNAL_ALL( &linda->write_happened ); + break; + + } else if (timeout==0.0) { + break; /* no wait; instant timeout */ + + } else { + /* limit faced; push until timeout */ + + cancel= cancel_test( L ); // testing here causes no delays + if (cancel) break; + + // K lock will be released for the duration of wait and re-acquired + // + if (!SIGNAL_WAIT( &linda->read_happened, &K->lock_, timeout )) + break; // timeout + } + } +STACK_END(KL,0) + } + keeper_release(K); + + if (cancel) + cancel_error(L); + + lua_pushboolean( L, ret ); + return 1; +} + + +/* +* [val, key]= linda_receive( linda_ud, [timeout_secs_num=-1], key_num|str|bool|lightuserdata [, ...] ) +* +* Receive a value from Linda, consuming it. +* +* Returns: value received (which is consumed from the slot) +* key which had it +*/ +LUAG_FUNC( linda_receive ) { + struct s_Linda *linda= lua_toLinda( L, 1 ); + int pushed; + bool_t cancel= FALSE; + struct s_Keeper *K; + time_d timeout= -1.0; + uint_t key_i= 2; + + if (lua_isnumber(L,2)) { + timeout= SIGNAL_TIMEOUT_PREPARE( lua_tonumber(L,2) ); + key_i++; + } else if (lua_isnil(L,2)) + key_i++; + + K= keeper_acquire( linda ); + { + while(TRUE) { + pushed= keeper_call( K->L, "receive", L, linda, key_i ); + if (pushed) { + ASSERT_L( pushed==2 ); + + // To be done from within the 'K' locking area + // + SIGNAL_ALL( &linda->read_happened ); + break; + + } else if (timeout==0.0) { + break; /* instant timeout */ + + } else { /* nothing received; wait until timeout */ + + cancel= cancel_test( L ); // testing here causes no delays + if (cancel) break; + + // Release the K lock for the duration of wait, and re-acquire + // + if (!SIGNAL_WAIT( &linda->write_happened, &K->lock_, timeout )) + break; + } + } + } + keeper_release(K); + + if (cancel) + cancel_error(L); + + return pushed; +} + + +/* +* = linda_set( linda_ud, key_num|str|bool|lightuserdata [,value] ) +* +* Set a value to Linda. +* +* Existing slot value is replaced, and possible queue entries removed. +*/ +LUAG_FUNC( linda_set ) { + struct s_Linda *linda= lua_toLinda( L, 1 ); + bool_t has_value= !lua_isnil(L,3); + + struct s_Keeper *K= keeper_acquire( linda ); + { + int pushed= keeper_call( K->L, "set", L, linda, 2 ); + ASSERT_L( pushed==0 ); + + /* Set the signal from within 'K' locking. + */ + if (has_value) { + SIGNAL_ALL( &linda->write_happened ); + } + } + keeper_release(K); + + return 0; +} + + +/* +* [val]= linda_get( linda_ud, key_num|str|bool|lightuserdata ) +* +* Get a value from Linda. +*/ +LUAG_FUNC( linda_get ) { + struct s_Linda *linda= lua_toLinda( L, 1 ); + int pushed; + + struct s_Keeper *K= keeper_acquire( linda ); + { + pushed= keeper_call( K->L, "get", L, linda, 2 ); + ASSERT_L( pushed==0 || pushed==1 ); + } + keeper_release(K); + + return pushed; +} + + +/* +* = linda_limit( linda_ud, key_num|str|bool|lightuserdata, uint [, ...] ) +* +* Set limits to 1 or more Linda keys. +*/ +LUAG_FUNC( linda_limit ) { + struct s_Linda *linda= lua_toLinda( L, 1 ); + + struct s_Keeper *K= keeper_acquire( linda ); + { + int pushed= keeper_call( K->L, "limit", L, linda, 2 ); + ASSERT_L( pushed==0 ); + } + keeper_release(K); + + return 0; +} + + +/* +* lightuserdata= linda_deep( linda_ud ) +* +* Return the 'deep' userdata pointer, identifying the Linda. +* +* This is needed for using Lindas as key indices (timer system needs it); +* separately created proxies of the same underlying deep object will have +* different userdata and won't be known to be essentially the same deep one +* without this. +*/ +LUAG_FUNC( linda_deep ) { + struct s_Linda *linda= lua_toLinda( L, 1 ); + lua_pushlightuserdata( L, linda ); // just the address + return 1; +} + + +/* +* Identity function of a shared userdata object. +* +* lightuserdata= linda_id( "new" [, ...] ) +* = linda_id( "delete", lightuserdata ) +* +* Creation and cleanup of actual 'deep' objects. 'luaG_...' will wrap them into +* regular userdata proxies, per each state using the deep data. +* +* tbl= linda_id( "metatable" ) +* +* Returns a metatable for the proxy objects ('__gc' method not needed; will +* be added by 'luaG_...') +* +* = linda_id( str, ... ) +* +* For any other strings, the ID function must not react at all. This allows +* future extensions of the system. +*/ +LUAG_FUNC( linda_id ) { + const char *which= lua_tostring(L,1); + + if (strcmp( which, "new" )==0) { + struct s_Linda *s; + + /* We don't use any parameters, but one could (they're at [2..TOS]) + */ + ASSERT_L( lua_gettop(L)==1 ); + + /* The deep data is allocated separately of Lua stack; we might no + * longer be around when last reference to it is being released. + * One can use any memory allocation scheme. + */ + s= (struct s_Linda *) malloc( sizeof(struct s_Linda) ); + ASSERT_L(s); + + SIGNAL_INIT( &s->read_happened ); + SIGNAL_INIT( &s->write_happened ); + + lua_pushlightuserdata( L, s ); + return 1; + + } else if (strcmp( which, "delete" )==0) { + struct s_Keeper *K; + struct s_Linda *s= lua_touserdata(L,2); + ASSERT_L(s); + + /* Clean associated structures in the keeper state. + */ + K= keeper_acquire(s); + { + keeper_call( K->L, "clear", L, s, 0 ); + } + keeper_release(K); + + /* There aren't any lanes waiting on these lindas, since all proxies + * have been gc'ed. Right? + */ + SIGNAL_FREE( &s->read_happened ); + SIGNAL_FREE( &s->write_happened ); + free(s); + + return 0; + + } else if (strcmp( which, "metatable" )==0) { + + STACK_CHECK(L) + lua_newtable(L); + lua_newtable(L); + // + // [-2]: linda metatable + // [-1]: metatable's to-be .__index table + + lua_pushcfunction( L, LG_linda_send ); + lua_setfield( L, -2, "send" ); + + lua_pushcfunction( L, LG_linda_receive ); + lua_setfield( L, -2, "receive" ); + + lua_pushcfunction( L, LG_linda_limit ); + lua_setfield( L, -2, "limit" ); + + lua_pushcfunction( L, LG_linda_set ); + lua_setfield( L, -2, "set" ); + + lua_pushcfunction( L, LG_linda_get ); + lua_setfield( L, -2, "get" ); + + lua_pushcfunction( L, LG_linda_deep ); + lua_setfield( L, -2, "deep" ); + + lua_setfield( L, -2, "__index" ); + STACK_END(L,1) + + return 1; + } + + return 0; // unknown request, be quiet +} + + +/*---=== Finalizer ===--- +*/ + +//--- +// void= finalizer( finalizer_func ) +// +// finalizer_func( [err, stack_tbl] ) +// +// Add a function that will be called when exiting the lane, either via +// normal return or an error. +// +LUAG_FUNC( set_finalizer ) +{ + STACK_GROW(L,3); + + // Get the current finalizer table (if any) + // + push_registry_table( L, FINALIZER_REG_KEY, TRUE /*do create if none*/ ); + + lua_pushinteger( L, lua_objlen(L,-1)+1 ); + lua_pushvalue( L, 1 ); // copy of the function + lua_settable( L, -3 ); + + lua_pop(L,1); + return 0; +} + + +//--- +// Run finalizers - if any - with the given parameters +// +// If 'rc' is nonzero, error message and stack index are available as: +// [-1]: stack trace (table) +// [-2]: error message (any type) +// +// Returns: +// 0 if finalizers were run without error (or there were none) +// LUA_ERRxxx return code if any of the finalizers failed +// +// TBD: should we add stack trace on failing finalizer, wouldn't be hard.. +// +static int run_finalizers( lua_State *L, int lua_rc ) +{ + unsigned error_index, tbl_index; + unsigned n; + int rc= 0; + + if (!push_registry_table(L, FINALIZER_REG_KEY, FALSE /*don't create one*/)) + return 0; // no finalizers + + tbl_index= lua_gettop(L); + error_index= (lua_rc!=0) ? tbl_index-1 : 0; // absolute indices + + STACK_GROW(L,4); + + // [-1]: { func [, ...] } + // + for( n= lua_objlen(L,-1); n>0; n-- ) { + unsigned args= 0; + lua_pushinteger( L,n ); + lua_gettable( L, -2 ); + + // [-1]: function + // [-2]: finalizers table + + if (error_index) { + lua_pushvalue( L, error_index ); + lua_pushvalue( L, error_index+1 ); // stack trace + args= 2; + } + + rc= lua_pcall( L, args, 0 /*retvals*/, 0 /*no errfunc*/ ); + // + // LUA_ERRRUN / LUA_ERRMEM + + if (rc!=0) { + // [-1]: error message + // + // If one finalizer fails, don't run the others. Return this + // as the 'real' error, preceding that we could have had (or not) + // from the actual code. + // + break; + } + } + + lua_remove(L,tbl_index); // take finalizer table out of stack + + return rc; +} + + +/*---=== Threads ===--- +*/ + +// NOTE: values to be changed by either thread, during execution, without +// locking, are marked "volatile" +// +struct s_lane { + THREAD_T thread; + // + // M: sub-thread OS thread + // S: not used + + lua_State *L; + // + // M: prepares the state, and reads results + // S: while S is running, M must keep out of modifying the state + + volatile enum e_status status; + // + // M: sets to PENDING (before launching) + // S: updates -> RUNNING/WAITING -> DONE/ERROR_ST/CANCELLED + + volatile bool_t cancel_request; + // + // M: sets to FALSE, flags TRUE for cancel request + // S: reads to see if cancel is requested + +#if !( (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) ) + SIGNAL_T done_signal_; + // + // M: Waited upon at lane ending (if Posix with no PTHREAD_TIMEDJOIN) + // S: sets the signal once cancellation is noticed (avoids a kill) + + MUTEX_T done_lock_; + // + // Lock required by 'done_signal' condition variable, protecting + // lane status changes to DONE/ERROR_ST/CANCELLED. +#endif + + volatile enum { + NORMAL, // normal master side state + KILLED // issued an OS kill + } mstatus; + // + // M: sets to NORMAL, if issued a kill changes to KILLED + // S: not used + + struct s_lane * volatile selfdestruct_next; + // + // M: sets to non-NULL if facing lane handle '__gc' cycle but the lane + // is still running + // S: cleans up after itself if non-NULL at lane exit +}; + +static MUTEX_T selfdestruct_cs; + // + // Protects modifying the selfdestruct chain + +#define SELFDESTRUCT_END ((struct s_lane *)(-1)) + // + // The chain is ended by '(struct s_lane*)(-1)', not NULL: + // 'selfdestruct_first -> ... -> ... -> (-1)' + +struct s_lane * volatile selfdestruct_first= SELFDESTRUCT_END; + +/* +* Add the lane to selfdestruct chain; the ones still running at the end of the +* whole process will be cancelled. +*/ +static void selfdestruct_add( struct s_lane *s ) { + + MUTEX_LOCK( &selfdestruct_cs ); + { + assert( s->selfdestruct_next == NULL ); + + s->selfdestruct_next= selfdestruct_first; + selfdestruct_first= s; + } + MUTEX_UNLOCK( &selfdestruct_cs ); +} + +/* +* A free-running lane has ended; remove it from selfdestruct chain +*/ +static void selfdestruct_remove( struct s_lane *s ) { + + MUTEX_LOCK( &selfdestruct_cs ); + { + // Make sure (within the MUTEX) that we actually are in the chain + // still (at process exit they will remove us from chain and then + // cancel/kill). + // + if (s->selfdestruct_next != NULL) { + struct s_lane **ref= (struct s_lane **) &selfdestruct_first; + bool_t found= FALSE; + + while( *ref != SELFDESTRUCT_END ) { + if (*ref == s) { + *ref= s->selfdestruct_next; + s->selfdestruct_next= NULL; + found= TRUE; + break; + } + ref= (struct s_lane **) &((*ref)->selfdestruct_next); + } + assert( found ); + } + } + MUTEX_UNLOCK( &selfdestruct_cs ); +} + +/* +* Process end; cancel any still free-running threads +*/ +static void selfdestruct_atexit( void ) { + + if (selfdestruct_first == SELFDESTRUCT_END) return; // no free-running threads + + // Signal _all_ still running threads to exit + // + MUTEX_LOCK( &selfdestruct_cs ); + { + struct s_lane *s= selfdestruct_first; + while( s != SELFDESTRUCT_END ) { + s->cancel_request= TRUE; + s= s->selfdestruct_next; + } + } + MUTEX_UNLOCK( &selfdestruct_cs ); + + // When noticing their cancel, the lanes will remove themselves from + // the selfdestruct chain. + + // TBD: Not sure if Windows (multi core) will require the timed approach, + // or single Yield. I don't have machine to test that (so leaving + // for timed approach). -- AKa 25-Oct-2008 + +#ifdef PLATFORM_LINUX + // It seems enough for Linux to have a single yield here, which allows + // other threads (timer lane) to proceed. Without the yield, there is + // segfault. + // + YIELD(); +#else + // OS X 10.5 (Intel) needs more to avoid segfaults. + // + // "make test" is okay. 100's of "make require" are okay. + // + // Tested on MacBook Core Duo 2GHz and 10.5.5: + // -- AKa 25-Oct-2008 + // + #ifndef ATEXIT_WAIT_SECS + # define ATEXIT_WAIT_SECS (0.1) + #endif + { + double t_until= now_secs() + ATEXIT_WAIT_SECS; + + while( selfdestruct_first != SELFDESTRUCT_END ) { + YIELD(); // give threads time to act on their cancel + + if (now_secs() >= t_until) break; + } + } +#endif + + //--- + // Kill the still free running threads + // + if ( selfdestruct_first != SELFDESTRUCT_END ) { + unsigned n=0; + MUTEX_LOCK( &selfdestruct_cs ); + { + struct s_lane *s= selfdestruct_first; + while( s != SELFDESTRUCT_END ) { + n++; + s= s->selfdestruct_next; + } + } + MUTEX_UNLOCK( &selfdestruct_cs ); + + // Linux (at least 64-bit): CAUSES A SEGFAULT IF THIS BLOCK IS ENABLED + // and works without the block (so let's leave those lanes running) + // +#if 1 + // 2.0.2: at least timer lane is still here + // + //fprintf( stderr, "Left %d lane(s) with cancel request at process end.\n", n ); +#else + MUTEX_LOCK( &selfdestruct_cs ); + { + struct s_lane *s= selfdestruct_first; + while( s != SELFDESTRUCT_END ) { + struct s_lane *next_s= s->selfdestruct_next; + s->selfdestruct_next= NULL; // detach from selfdestruct chain + + THREAD_KILL( &s->thread ); + s= next_s; + n++; + } + selfdestruct_first= SELFDESTRUCT_END; + } + MUTEX_UNLOCK( &selfdestruct_cs ); + + fprintf( stderr, "Killed %d lane(s) at process end.\n", n ); +#endif + } +} + + +// To allow free-running threads (longer lifespan than the handle's) +// 'struct s_lane' are malloc/free'd and the handle only carries a pointer. +// This is not deep userdata since the handle's not portable among lanes. +// +#define lua_toLane(L,i) (* ((struct s_lane**) lua_touserdata(L,i))) + + +/* +* Check if the thread in question ('L') has been signalled for cancel. +* +* Called by cancellation hooks and/or pending Linda operations (because then +* the check won't affect performance). +* +* Returns TRUE if any locks are to be exited, and 'cancel_error()' called, +* to make execution of the lane end. +*/ +static bool_t cancel_test( lua_State *L ) { + struct s_lane *s; + + STACK_GROW(L,1); + + STACK_CHECK(L) + lua_pushlightuserdata( L, CANCEL_TEST_KEY ); + lua_rawget( L, LUA_REGISTRYINDEX ); + s= lua_touserdata( L, -1 ); // lightuserdata (true 's_lane' pointer) / nil + lua_pop(L,1); + STACK_END(L,0) + + // 's' is NULL for the original main state (no-one can cancel that) + // + return s && s->cancel_request; +} + +static void cancel_error( lua_State *L ) { + STACK_GROW(L,1); + lua_pushlightuserdata( L, CANCEL_ERROR ); // special error value + lua_error(L); // no return +} + +static void cancel_hook( lua_State *L, lua_Debug *ar ) { + (void)ar; + if (cancel_test(L)) cancel_error(L); +} + + +//--- +// = _single( [cores_uint=1] ) +// +// Limits the process to use only 'cores' CPU cores. To be used for performance +// testing on multicore devices. DEBUGGING ONLY! +// +LUAG_FUNC( _single ) { + uint_t cores= luaG_optunsigned(L,1,1); + +#ifdef PLATFORM_OSX + #ifdef _UTILBINDTHREADTOCPU + if (cores > 1) luaL_error( L, "Limiting to N>1 cores not possible." ); + // requires 'chudInitialize()' + utilBindThreadToCPU(0); // # of CPU to run on (we cannot limit to 2..N CPUs?) + #else + luaL_error( L, "Not available: compile with _UTILBINDTHREADTOCPU" ); + #endif +#else + luaL_error( L, "not implemented!" ); +#endif + (void)cores; + + return 0; +} + + +/* +* str= lane_error( error_val|str ) +* +* Called if there's an error in some lane; add call stack to error message +* just like 'lua.c' normally does. +* +* ".. will be called with the error message and its return value will be the +* message returned on the stack by lua_pcall." +* +* Note: Rather than modifying the error message itself, it would be better +* to provide the call stack (as string) completely separated. This would +* work great with non-string error values as well (current system does not). +* (This is NOT possible with the Lua 5.1 'lua_pcall()'; we could of course +* implement a Lanes-specific 'pcall' of our own that does this). TBD!!! :) +* --AKa 22-Jan-2009 +*/ +#ifdef ERROR_FULL_STACK + +static int lane_error( lua_State *L ) { + lua_Debug ar; + unsigned lev,n; + + // [1]: error message (any type) + + assert( lua_gettop(L)==1 ); + + // Don't do stack survey for cancelled lanes. + // +#if 1 + if (lua_touserdata(L,1) == CANCEL_ERROR) + return 1; // just pass on +#endif + + // Place stack trace at 'registry[lane_error]' for the 'luc_pcall()' + // caller to fetch. This bypasses the Lua 5.1 limitation of only one + // return value from error handler to 'lua_pcall()' caller. + + // It's adequate to push stack trace as a table. This gives the receiver + // of the stack best means to format it to their liking. Also, it allows + // us to add more stack info later, if needed. + // + // table of { "sourcefile.lua:<line>", ... } + // + STACK_GROW(L,3); + lua_newtable(L); + + // Best to start from level 1, but in some cases it might be a C function + // and we don't get '.currentline' for that. It's okay - just keep level + // and table index growing separate. --AKa 22-Jan-2009 + // + lev= 0; + n=1; + while( lua_getstack(L, ++lev, &ar ) ) { + lua_getinfo(L, "Sl", &ar); + if (ar.currentline > 0) { + lua_pushinteger( L, n++ ); + lua_pushfstring( L, "%s:%d", ar.short_src, ar.currentline ); + lua_settable( L, -3 ); + } + } + + lua_pushlightuserdata( L, STACK_TRACE_KEY ); + lua_insert(L,-2); + lua_settable( L, LUA_REGISTRYINDEX ); + + assert( lua_gettop(L)== 1 ); + + return 1; // the untouched error value +} +#endif + + +//--- +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) + static THREAD_RETURN_T __stdcall lane_main( void *vs ) +#else + static THREAD_RETURN_T lane_main( void *vs ) +#endif +{ + struct s_lane *s= (struct s_lane *)vs; + int rc, rc2; + lua_State *L= s->L; + + s->status= RUNNING; // PENDING -> RUNNING + + // Tie "set_finalizer()" to the state + // + lua_pushcfunction( L, LG_set_finalizer ); + lua_setglobal( L, "set_finalizer" ); + +#ifdef ERROR_FULL_STACK + STACK_GROW( L, 1 ); + lua_pushcfunction( L, lane_error ); + lua_insert( L, 1 ); + + // [1]: error handler + // [2]: function to run + // [3..top]: parameters + // + rc= lua_pcall( L, lua_gettop(L)-2, LUA_MULTRET, 1 /*error handler*/ ); + // 0: no error + // LUA_ERRRUN: a runtime error (error pushed on stack) + // LUA_ERRMEM: memory allocation error + // LUA_ERRERR: error while running the error handler (if any) + + assert( rc!=LUA_ERRERR ); // since we've authored it + + lua_remove(L,1); // remove error handler + + // Lua 5.1 error handler is limited to one return value; taking stack trace + // via registry + // + if (rc!=0) { + STACK_GROW(L,1); + lua_pushlightuserdata( L, STACK_TRACE_KEY ); + lua_gettable(L, LUA_REGISTRYINDEX); + + // For cancellation, a stack trace isn't placed + // + assert( lua_istable(L,2) || (lua_touserdata(L,1)==CANCEL_ERROR) ); + + // Just leaving the stack trace table on the stack is enough to get + // it through to the master. + } + +#else + // This code does not use 'lane_error' + // + // [1]: function to run + // [2..top]: parameters + // + rc= lua_pcall( L, lua_gettop(L)-1, LUA_MULTRET, 0 /*no error handler*/ ); + // 0: no error + // LUA_ERRRUN: a runtime error (error pushed on stack) + // LUA_ERRMEM: memory allocation error +#endif + +//STACK_DUMP(L); + // Call finalizers, if the script has set them up. + // + rc2= run_finalizers(L,rc); + if (rc2!=0) { + // Error within a finalizer! + // + // [-1]: error message + + rc= rc2; // we're overruling the earlier script error or normal return + + lua_insert( L,1 ); // make error message [1] + lua_settop( L,1 ); // remove all rest + + // Place an empty stack table just to keep the API simple (always when + // there's an error, there's also stack table - though it may be empty). + // + lua_newtable(L); + } + + if (s->selfdestruct_next != NULL) { + // We're a free-running thread and no-one's there to clean us up. + // + lua_close( s->L ); + L= 0; + + #if !( (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) ) + SIGNAL_FREE( &s->done_signal_ ); + MUTEX_FREE( &s->done_lock_ ); + #endif + selfdestruct_remove(s); // away from selfdestruct chain + free(s); + + } else { + // leave results (1..top) or error message + stack trace (1..2) on the stack - master will copy them + + enum e_status st= + (rc==0) ? DONE + : (lua_touserdata(L,1)==CANCEL_ERROR) ? CANCELLED + : ERROR_ST; + + // Posix no PTHREAD_TIMEDJOIN: + // 'done_lock' protects the -> DONE|ERROR_ST|CANCELLED state change + // + #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) + s->status= st; + #else + MUTEX_LOCK( &s->done_lock_ ); + { + s->status= st; + SIGNAL_ONE( &s->done_signal_ ); // wake up master (while 's->done_lock' is on) + } + MUTEX_UNLOCK( &s->done_lock_ ); + #endif + } + + return 0; // ignored +} + + +//--- +// lane_ud= thread_new( function, [libs_str], +// [cancelstep_uint=0], +// [prio_int=0], +// [globals_tbl], +// [... args ...] ) +// +// Upvalues: metatable to use for 'lane_ud' +// +LUAG_FUNC( thread_new ) +{ + lua_State *L2; + struct s_lane *s; + struct s_lane **ud; + + const char *libs= lua_tostring( L, 2 ); + uint_t cs= luaG_optunsigned( L, 3,0); + int prio= luaL_optinteger( L, 4,0); + uint_t glob= luaG_isany(L,5) ? 5:0; + + #define FIXED_ARGS (5) + uint_t args= lua_gettop(L) - FIXED_ARGS; + + if (prio < THREAD_PRIO_MIN || prio > THREAD_PRIO_MAX) { + luaL_error( L, "Priority out of range: %d..+%d (%d)", + THREAD_PRIO_MIN, THREAD_PRIO_MAX, prio ); + } + + /* --- Create and prepare the sub state --- */ + + L2 = luaL_newstate(); // uses standard 'realloc()'-based allocator, + // sets the panic callback + + if (!L2) luaL_error( L, "'luaL_newstate()' failed; out of memory" ); + + STACK_GROW( L,2 ); + + // Setting the globals table (needs to be done before loading stdlibs, + // and the lane function) + // + if (glob!=0) { +STACK_CHECK(L) + if (!lua_istable(L,glob)) + luaL_error( L, "Expected table, got %s", luaG_typename(L,glob) ); + + lua_pushvalue( L, glob ); + luaG_inter_move( L,L2, 1 ); // moves the table to L2 + + // L2 [-1]: table of globals + + // "You can change the global environment of a Lua thread using lua_replace" + // (refman-5.0.pdf p. 30) + // + lua_replace( L2, LUA_GLOBALSINDEX ); +STACK_END(L,0) + } + + // Selected libraries + // + if (libs) { + const char *err= luaG_openlibs( L2, libs ); + ASSERT_L( !err ); // bad libs should have been noticed by 'lanes.lua' + + serialize_require( L2 ); + } + + // Lane main function + // +STACK_CHECK(L) + lua_pushvalue( L, 1 ); + luaG_inter_move( L,L2, 1 ); // L->L2 +STACK_MID(L,0) + + ASSERT_L( lua_gettop(L2) == 1 ); + ASSERT_L( lua_isfunction(L2,1) ); + + // revive arguments + // + if (args) luaG_inter_copy( L,L2, args ); // L->L2 +STACK_MID(L,0) + +ASSERT_L( (uint_t)lua_gettop(L2) == 1+args ); +ASSERT_L( lua_isfunction(L2,1) ); + + // 's' is allocated from heap, not Lua, since its life span may surpass + // the handle's (if free running thread) + // + ud= lua_newuserdata( L, sizeof(struct s_lane*) ); + ASSERT_L(ud); + + s= *ud= malloc( sizeof(struct s_lane) ); + ASSERT_L(s); + + //memset( s, 0, sizeof(struct s_lane) ); + s->L= L2; + s->status= PENDING; + s->cancel_request= FALSE; + +#if !( (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) ) + MUTEX_INIT( &s->done_lock_ ); + SIGNAL_INIT( &s->done_signal_ ); +#endif + s->mstatus= NORMAL; + s->selfdestruct_next= NULL; + + // Set metatable for the userdata + // + lua_pushvalue( L, lua_upvalueindex(1) ); + lua_setmetatable( L, -2 ); +STACK_MID(L,1) + + // Place 's' to registry, for 'cancel_test()' (even if 'cs'==0 we still + // do cancel tests at pending send/receive). + // + lua_pushlightuserdata( L2, CANCEL_TEST_KEY ); + lua_pushlightuserdata( L2, s ); + lua_rawset( L2, LUA_REGISTRYINDEX ); + + if (cs) { + lua_sethook( L2, cancel_hook, LUA_MASKCOUNT, cs ); + } + + THREAD_CREATE( &s->thread, lane_main, s, prio ); +STACK_END(L,1) + + return 1; +} + + +//--- +// = thread_gc( lane_ud ) +// +// Cleanup for a thread userdata. If the thread is still executing, leave it +// alive as a free-running thread (will clean up itself). +// +// * Why NOT cancel/kill a loose thread: +// +// At least timer system uses a free-running thread, they should be handy +// and the issue of cancelling/killing threads at gc is not very nice, either +// (would easily cause waits at gc cycle, which we don't want). +// +// * Why YES kill a loose thread: +// +// Current way causes segfaults at program exit, if free-running threads are +// in certain stages. Details are not clear, but this is the core reason. +// If gc would kill threads then at process exit only one thread would remain. +// +// Todo: Maybe we should have a clear #define for selecting either behaviour. +// +LUAG_FUNC( thread_gc ) { + struct s_lane *s= lua_toLane(L,1); + + // We can read 's->status' without locks, but not wait for it + // + if (s->status < DONE) { + // + selfdestruct_add(s); + assert( s->selfdestruct_next ); + return 0; + + } else if (s->mstatus==KILLED) { + // Make sure a kill has proceeded, before cleaning up the data structure. + // + // If not doing 'THREAD_WAIT()' we should close the Lua state here + // (can it be out of order, since we killed the lane abruptly?) + // +#if 0 + lua_close( s->L ); +#else +fprintf( stderr, "** Joining with a killed thread (needs testing) **" ); +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) + THREAD_WAIT( &s->thread, -1 ); +#else + THREAD_WAIT( &s->thread, &s->done_signal_, &s->done_lock_, &s->status, -1 ); +#endif +fprintf( stderr, "** Joined ok **" ); +#endif + } + + // Clean up after a (finished) thread + // +#if (! ((defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN))) + SIGNAL_FREE( &s->done_signal_ ); + MUTEX_FREE( &s->done_lock_ ); + free(s); +#endif + + return 0; +} + + +//--- +// = thread_cancel( lane_ud [,timeout_secs=0.0] [,force_kill_bool=false] ) +// +// The originator thread asking us specifically to cancel the other thread. +// +// 'timeout': <0: wait forever, until the lane is finished +// 0.0: just signal it to cancel, no time waited +// >0: time to wait for the lane to detect cancellation +// +// 'force_kill': if true, and lane does not detect cancellation within timeout, +// it is forcefully killed. Using this with 0.0 timeout means just kill +// (unless the lane is already finished). +// +// Returns: true if the lane was already finished (DONE/ERROR_ST/CANCELLED) or if we +// managed to cancel it. +// false if the cancellation timed out, or a kill was needed. +// +LUAG_FUNC( thread_cancel ) +{ + struct s_lane *s= lua_toLane(L,1); + double secs= 0.0; + uint_t force_i=2; + bool_t force, done= TRUE; + + if (lua_isnumber(L,2)) { + secs= lua_tonumber(L,2); + force_i++; + } else if (lua_isnil(L,2)) + force_i++; + + force= lua_toboolean(L,force_i); // FALSE if nothing there + + // We can read 's->status' without locks, but not wait for it (if Posix no PTHREAD_TIMEDJOIN) + // + if (s->status < DONE) { + s->cancel_request= TRUE; // it's now signalled to stop + + done= thread_cancel( s, secs, force ); + } + + lua_pushboolean( L, done ); + return 1; +} + +static bool_t thread_cancel( struct s_lane *s, double secs, bool_t force ) +{ + bool_t done= +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) + THREAD_WAIT( &s->thread, secs ); +#else + THREAD_WAIT( &s->thread, &s->done_signal_, &s->done_lock_, &s->status, secs ); +#endif + + if ((!done) && force) { + // Killing is asynchronous; we _will_ wait for it to be done at + // GC, to make sure the data structure can be released (alternative + // would be use of "cancellation cleanup handlers" that at least + // PThread seems to have). + // + THREAD_KILL( &s->thread ); + s->mstatus= KILLED; // mark 'gc' to wait for it + } + return done; +} + + +//--- +// str= thread_status( lane_ud ) +// +// Returns: "pending" not started yet +// -> "running" started, doing its work.. +// <-> "waiting" blocked in a receive() +// -> "done" finished, results are there +// / "error" finished at an error, error value is there +// / "cancelled" execution cancelled by M (state gone) +// +LUAG_FUNC( thread_status ) +{ + struct s_lane *s= lua_toLane(L,1); + enum e_status st= s->status; // read just once (volatile) + const char *str; + + if (s->mstatus == KILLED) + st= CANCELLED; + + str= (st==PENDING) ? "pending" : + (st==RUNNING) ? "running" : // like in 'co.status()' + (st==WAITING) ? "waiting" : + (st==DONE) ? "done" : + (st==ERROR_ST) ? "error" : + (st==CANCELLED) ? "cancelled" : NULL; + ASSERT_L(str); + + lua_pushstring( L, str ); + return 1; +} + + +//--- +// [...] | [nil, err_any, stack_tbl]= thread_join( lane_ud [, wait_secs=-1] ) +// +// timeout: returns nil +// done: returns return values (0..N) +// error: returns nil + error value + stack table +// cancelled: returns nil +// +LUAG_FUNC( thread_join ) +{ + struct s_lane *s= lua_toLane(L,1); + double wait_secs= luaL_optnumber(L,2,-1.0); + lua_State *L2= s->L; + int ret; + + bool_t done= +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) + THREAD_WAIT( &s->thread, wait_secs ); +#else + THREAD_WAIT( &s->thread, &s->done_signal_, &s->done_lock_, &s->status, wait_secs ); +#endif + if (!done) + return 0; // timeout: pushes none, leaves 'L2' alive + + // Thread is DONE/ERROR_ST/CANCELLED; all ours now + + STACK_GROW( L, 1 ); + + switch( s->status ) { + case DONE: { + uint_t n= lua_gettop(L2); // whole L2 stack + luaG_inter_move( L2,L, n ); + ret= n; + } break; + + case ERROR_ST: + lua_pushnil(L); + luaG_inter_move( L2,L, 2 ); // error message at [-2], stack trace at [-1] + ret= 3; + break; + + case CANCELLED: + ret= 0; + break; + + default: + fprintf( stderr, "Status: %d\n", s->status ); + ASSERT_L( FALSE ); ret= 0; + } + lua_close(L2); + + return ret; +} + + +/*---=== Timer support ===--- +*/ + +/* +* Push a timer gateway Linda object; only one deep userdata is +* created for this, each lane will get its own proxy. +* +* Note: this needs to be done on the C side; Lua wouldn't be able +* to even see, when we've been initialized for the very first +* time (with us, they will be). +*/ +static +void push_timer_gateway( lua_State *L ) { + + /* No need to lock; 'static' is just fine + */ + static DEEP_PRELUDE *p; // = NULL + + STACK_CHECK(L) + if (!p) { + // Create the Linda (only on first time) + // + // proxy_ud= deep_userdata( idfunc ) + // + lua_pushcfunction( L, luaG_deep_userdata ); + lua_pushcfunction( L, LG_linda_id ); + lua_call( L, 1 /*args*/, 1 /*retvals*/ ); + + ASSERT_L( lua_isuserdata(L,-1) ); + + // Proxy userdata contents is only a 'DEEP_PRELUDE*' pointer + // + p= * (DEEP_PRELUDE**) lua_touserdata( L, -1 ); + ASSERT_L(p && p->refcount==1 && p->deep); + + // [-1]: proxy for accessing the Linda + + } else { + /* Push a proxy based on the deep userdata we stored. + */ + luaG_push_proxy( L, LG_linda_id, p ); + } + STACK_END(L,1) +} + +/* +* secs= now_secs() +* +* Returns the current time, as seconds (millisecond resolution). +*/ +LUAG_FUNC( now_secs ) +{ + lua_pushnumber( L, now_secs() ); + return 1; +} + +/* +* wakeup_at_secs= wakeup_conv( date_tbl ) +*/ +LUAG_FUNC( wakeup_conv ) +{ + int year, month, day, hour, min, sec, isdst; + struct tm tm= {0}; + // + // .year (four digits) + // .month (1..12) + // .day (1..31) + // .hour (0..23) + // .min (0..59) + // .sec (0..61) + // .yday (day of the year) + // .isdst (daylight saving on/off) + + STACK_CHECK(L) + lua_getfield( L, 1, "year" ); year= lua_tointeger(L,-1); lua_pop(L,1); + lua_getfield( L, 1, "month" ); month= lua_tointeger(L,-1); lua_pop(L,1); + lua_getfield( L, 1, "day" ); day= lua_tointeger(L,-1); lua_pop(L,1); + lua_getfield( L, 1, "hour" ); hour= lua_tointeger(L,-1); lua_pop(L,1); + lua_getfield( L, 1, "min" ); min= lua_tointeger(L,-1); lua_pop(L,1); + lua_getfield( L, 1, "sec" ); sec= lua_tointeger(L,-1); lua_pop(L,1); + + // If Lua table has '.isdst' we trust that. If it does not, we'll let + // 'mktime' decide on whether the time is within DST or not (value -1). + // + lua_getfield( L, 1, "isdst" ); + isdst= lua_isboolean(L,-1) ? lua_toboolean(L,-1) : -1; + lua_pop(L,1); + STACK_END(L,0) + + tm.tm_year= year-1900; + tm.tm_mon= month-1; // 0..11 + tm.tm_mday= day; // 1..31 + tm.tm_hour= hour; // 0..23 + tm.tm_min= min; // 0..59 + tm.tm_sec= sec; // 0..60 + tm.tm_isdst= isdst; // 0/1/negative + + lua_pushnumber( L, (double) mktime( &tm ) ); // ms=0 + return 1; +} + + +/*---=== Module linkage ===--- +*/ + +#define REG_FUNC( name ) \ + lua_pushcfunction( L, LG_##name ); \ + lua_setglobal( L, #name ) + +#define REG_FUNC2( name, val ) \ + lua_pushcfunction( L, val ); \ + lua_setglobal( L, #name ) + +#define REG_STR2( name, val ) \ + lua_pushstring( L, val ); \ + lua_setglobal( L, #name ) + +#define REG_INT2( name, val ) \ + lua_pushinteger( L, val ); \ + lua_setglobal( L, #name ) + + +int +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) +__declspec(dllexport) +#endif + luaopen_lanes( lua_State *L ) { + const char *err; + static volatile char been_here; // =0 + + // One time initializations: + // + if (!been_here) { + been_here= TRUE; + +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) + now_secs(); // initialize 'now_secs()' internal offset +#endif + +#if (defined PLATFORM_OSX) && (defined _UTILBINDTHREADTOCPU) + chudInitialize(); +#endif + + // Locks for 'tools.c' inc/dec counters + // + MUTEX_INIT( &deep_lock ); + MUTEX_INIT( &mtid_lock ); + + // Serialize calls to 'require' from now on, also in the primary state + // + MUTEX_RECURSIVE_INIT( &require_cs ); + + serialize_require( L ); + + // Selfdestruct chain handling + // + MUTEX_INIT( &selfdestruct_cs ); + atexit( selfdestruct_atexit ); + + //--- + // Linux needs SCHED_RR to change thread priorities, and that is only + // allowed for sudo'ers. SCHED_OTHER (default) has no priorities. + // SCHED_OTHER threads are always lower priority than SCHED_RR. + // + // ^-- those apply to 2.6 kernel. IF **wishful thinking** these + // constraints will change in the future, non-sudo priorities can + // be enabled also for Linux. + // +#ifdef PLATFORM_LINUX + sudo= geteuid()==0; // we are root? + + // If lower priorities (-2..-1) are wanted, we need to lift the main + // thread to SCHED_RR and 50 (medium) level. Otherwise, we're always below + // the launched threads (even -2). + // + #ifdef LINUX_SCHED_RR + if (sudo) { + struct sched_param sp= {0}; sp.sched_priority= _PRIO_0; + PT_CALL( pthread_setschedparam( pthread_self(), SCHED_RR, &sp) ); + } + #endif +#endif + err= init_keepers(); + if (err) + luaL_error( L, "Unable to initialize: %s", err ); + } + + // Linda identity function + // + REG_FUNC( linda_id ); + + // metatable for threads + // + lua_newtable( L ); + lua_pushcfunction( L, LG_thread_gc ); + lua_setfield( L, -2, "__gc" ); + + lua_pushcclosure( L, LG_thread_new, 1 ); // metatable as closure param + lua_setglobal( L, "thread_new" ); + + REG_FUNC( thread_status ); + REG_FUNC( thread_join ); + REG_FUNC( thread_cancel ); + + REG_STR2( _version, VERSION ); + REG_FUNC( _single ); + + REG_FUNC2( _deep_userdata, luaG_deep_userdata ); + + REG_FUNC( now_secs ); + REG_FUNC( wakeup_conv ); + + push_timer_gateway(L); + lua_setglobal( L, "timer_gateway" ); + + REG_INT2( max_prio, THREAD_PRIO_MAX ); + + lua_pushlightuserdata( L, CANCEL_ERROR ); + lua_setglobal( L, "cancel_error" ); + + return 0; +} + + diff --git a/src/lualanes/threading.c b/src/lualanes/threading.c new file mode 100644 index 0000000000000000000000000000000000000000..68d1e41f2f338ef7defbb9d508e69f715926cc46 --- /dev/null +++ b/src/lualanes/threading.c @@ -0,0 +1,721 @@ +/* + * THREADING.C Copyright (c) 2007-08, Asko Kauppi + * + * Lua Lanes OS threading specific code. + * + * References: + * <http://www.cse.wustl.edu/~schmidt/win32-cv-1.html> +*/ + +/* +=============================================================================== + +Copyright (C) 2007-08 Asko Kauppi <akauppi@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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=============================================================================== +*/ +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <math.h> + +#include "threading.h" +#include "lua.h" + +#if !((defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)) +# include <sys/time.h> +#endif + + +#if defined(PLATFORM_LINUX) || defined(PLATFORM_CYGWIN) +# include <sys/types.h> +# include <unistd.h> +#endif + +/* Linux needs to check, whether it's been run as root +*/ +#ifdef PLATFORM_LINUX + volatile bool_t sudo; +#endif + +#ifdef _MSC_VER +// ".. selected for automatic inline expansion" (/O2 option) +# pragma warning( disable : 4711 ) +// ".. type cast from function pointer ... to data pointer" +# pragma warning( disable : 4054 ) +#endif + +//#define THREAD_CREATE_RETRIES_MAX 20 + // loops (maybe retry forever?) + +/* +* FAIL is for unexpected API return values - essentially programming +* error in _this_ code. +*/ +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) +static void FAIL( const char *funcname, int rc ) { + fprintf( stderr, "%s() failed! (%d)\n", funcname, rc ); + abort(); +} +#endif + + +/* +* Returns millisecond timing (in seconds) for the current time. +* +* Note: This function should be called once in single-threaded mode in Win32, +* to get it initialized. +*/ +time_d now_secs(void) { + +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) + /* + * Windows FILETIME values are "100-nanosecond intervals since + * January 1, 1601 (UTC)" (MSDN). Well, we'd want Unix Epoch as + * the offset and it seems, so would they: + * + * <http://msdn.microsoft.com/en-us/library/ms724928(VS.85).aspx> + */ + SYSTEMTIME st; + FILETIME ft; + ULARGE_INTEGER uli; + static ULARGE_INTEGER uli_epoch; // Jan 1st 1970 0:0:0 + + if (uli_epoch.HighPart==0) { + st.wYear= 1970; + st.wMonth= 1; // Jan + st.wDay= 1; + st.wHour= st.wMinute= st.wSecond= st.wMilliseconds= 0; + + if (!SystemTimeToFileTime( &st, &ft )) + FAIL( "SystemTimeToFileTime", GetLastError() ); + + uli_epoch.LowPart= ft.dwLowDateTime; + uli_epoch.HighPart= ft.dwHighDateTime; + } + + GetSystemTime( &st ); // current system date/time in UTC + if (!SystemTimeToFileTime( &st, &ft )) + FAIL( "SystemTimeToFileTime", GetLastError() ); + + uli.LowPart= ft.dwLowDateTime; + uli.HighPart= ft.dwHighDateTime; + + /* 'double' has less accuracy than 64-bit int, but if it were to degrade, + * it would do so gracefully. In practise, the integer accuracy is not + * of the 100ns class but just 1ms (Windows XP). + */ +# if 1 + // >= 2.0.3 code + return (double) ((uli.QuadPart - uli_epoch.QuadPart)/10000) / 1000.0; +# elif 0 + // fix from Kriss Daniels, see: + // <http://luaforge.net/forum/forum.php?thread_id=22704&forum_id=1781> + // + // "seem to be getting negative numbers from the old version, probably number + // conversion clipping, this fixes it and maintains ms resolution" + // + // This was a bad fix, and caused timer test 5 sec timers to disappear. + // --AKa 25-Jan-2009 + // + return ((double)((signed)((uli.QuadPart/10000) - (uli_epoch.QuadPart/10000)))) / 1000.0; +# else + // <= 2.0.2 code + return (double)(uli.QuadPart - uli_epoch.QuadPart) / 10000000.0; +# endif +#else + struct timeval tv; + // { + // time_t tv_sec; /* seconds since Jan. 1, 1970 */ + // suseconds_t tv_usec; /* and microseconds */ + // }; + + int rc= gettimeofday( &tv, NULL /*time zone not used any more (in Linux)*/ ); + assert( rc==0 ); + + return ((double)tv.tv_sec) + ((tv.tv_usec)/1000) / 1000.0; +#endif +} + + +/* +*/ +time_d SIGNAL_TIMEOUT_PREPARE( double secs ) { + if (secs<=0.0) return secs; + else return now_secs() + secs; +} + + +#if !((defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC)) +/* +* Prepare 'abs_secs' kind of timeout to 'timespec' format +*/ +static void prepare_timeout( struct timespec *ts, time_d abs_secs ) { + assert(ts); + assert( abs_secs >= 0.0 ); + + if (abs_secs==0.0) + abs_secs= now_secs(); + + ts->tv_sec= floor( abs_secs ); + ts->tv_nsec= ((long)((abs_secs - ts->tv_sec) * 1000.0 +0.5)) * 1000000UL; // 1ms = 1000000ns +} +#endif + + +/*---=== Threading ===---*/ + +//--- +// It may be meaningful to explicitly limit the new threads' C stack size. +// We should know how much Lua needs in the C stack, all Lua side allocations +// are done in heap so they don't count. +// +// Consequence of _not_ limiting the stack is running out of virtual memory +// with 1000-5000 threads on 32-bit systems. +// +// Note: using external C modules may be affected by the stack size check. +// if having problems, set back to '0' (default stack size of the system). +// +// Win32: 64K (?) +// Win64: xxx +// +// Linux x86: 2MB Ubuntu 7.04 via 'pthread_getstacksize()' +// Linux x64: xxx +// Linux ARM: xxx +// +// OS X 10.4.9: 512K <http://developer.apple.com/qa/qa2005/qa1419.html> +// valid values N * 4KB +// +#ifndef _THREAD_STACK_SIZE +# if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PLATFORM_CYGWIN) +# define _THREAD_STACK_SIZE 0 + // Win32: does it work with less? +# elif (defined PLATFORM_OSX) +# define _THREAD_STACK_SIZE (524288/2) // 262144 + // OS X: "make test" works on 65536 and even below + // "make perftest" works on >= 4*65536 == 262144 (not 3*65536) +# elif (defined PLATFORM_LINUX) && (defined __i386) +# define _THREAD_STACK_SIZE (2097152/16) // 131072 + // Linux x86 (Ubuntu 7.04): "make perftest" works on /16 (not on /32) +# elif (defined PLATFORM_BSD) && (defined __i386) +# define _THREAD_STACK_SIZE (1048576/8) // 131072 + // FreeBSD 6.2 SMP i386: ("gmake perftest" works on /8 (not on /16) +# endif +#endif + +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) + // + void MUTEX_INIT( MUTEX_T *ref ) { + *ref= CreateMutex( NULL /*security attr*/, FALSE /*not locked*/, NULL ); + if (!ref) FAIL( "CreateMutex", GetLastError() ); + } + void MUTEX_FREE( MUTEX_T *ref ) { + if (!CloseHandle(*ref)) FAIL( "CloseHandle (mutex)", GetLastError() ); + *ref= NULL; + } + void MUTEX_LOCK( MUTEX_T *ref ) { + DWORD rc= WaitForSingleObject(*ref,INFINITE); + if (rc!=0) FAIL( "WaitForSingleObject", rc==WAIT_FAILED ? GetLastError() : rc ); + } + void MUTEX_UNLOCK( MUTEX_T *ref ) { + if (!ReleaseMutex(*ref)) + FAIL( "ReleaseMutex", GetLastError() ); + } + /* MSDN: "If you would like to use the CRT in ThreadProc, use the + _beginthreadex function instead (of CreateThread)." + MSDN: "you can create at most 2028 threads" + */ + void + THREAD_CREATE( THREAD_T *ref, + THREAD_RETURN_T (__stdcall *func)( void * ), + // Note: Visual C++ requires '__stdcall' where it is + void *data, int prio /* -3..+3 */ ) { + + HANDLE h= (HANDLE)_beginthreadex( NULL, // security + _THREAD_STACK_SIZE, + func, + data, + 0, // flags (0/CREATE_SUSPENDED) + NULL // thread id (not used) + ); + + if (h == INVALID_HANDLE_VALUE) FAIL( "CreateThread", GetLastError() ); + + if (prio!= 0) { + int win_prio= (prio == +3) ? THREAD_PRIORITY_TIME_CRITICAL : + (prio == +2) ? THREAD_PRIORITY_HIGHEST : + (prio == +1) ? THREAD_PRIORITY_ABOVE_NORMAL : + (prio == -1) ? THREAD_PRIORITY_BELOW_NORMAL : + (prio == -2) ? THREAD_PRIORITY_LOWEST : + THREAD_PRIORITY_IDLE; // -3 + + if (!SetThreadPriority( h, win_prio )) + FAIL( "SetThreadPriority", GetLastError() ); + } + *ref= h; + } + // + bool_t THREAD_WAIT( THREAD_T *ref, double secs ) { + long ms= (long)((secs*1000.0)+0.5); + + DWORD rc= WaitForSingleObject( *ref, ms<0 ? INFINITE:ms /*timeout*/ ); + // + // (WAIT_ABANDONED) + // WAIT_OBJECT_0 success (0) + // WAIT_TIMEOUT + // WAIT_FAILED more info via GetLastError() + + if (rc == WAIT_TIMEOUT) return FALSE; + if (rc != 0) FAIL( "WaitForSingleObject", rc ); + *ref= NULL; // thread no longer usable + return TRUE; + } + // + void THREAD_KILL( THREAD_T *ref ) { + if (!TerminateThread( *ref, 0 )) FAIL("TerminateThread", GetLastError()); + *ref= NULL; + } + // + void SIGNAL_INIT( SIGNAL_T *ref ) { + // 'manual reset' event type selected, to be able to wake up all the + // waiting threads. + // + HANDLE h= CreateEvent( NULL, // security attributes + TRUE, // TRUE: manual event + FALSE, // Initial state + NULL ); // name + + if (h == NULL) FAIL( "CreateEvent", GetLastError() ); + *ref= h; + } + void SIGNAL_FREE( SIGNAL_T *ref ) { + if (!CloseHandle(*ref)) FAIL( "CloseHandle (event)", GetLastError() ); + *ref= NULL; + } + // + bool_t SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu_ref, time_d abs_secs ) { + DWORD rc; + long ms; + + if (abs_secs<0.0) + ms= INFINITE; + else if (abs_secs==0.0) + ms= 0; + else { + ms= (long) ((abs_secs - now_secs())*1000.0 + 0.5); + + // If the time already passed, still try once (ms==0). A short timeout + // may have turned negative or 0 because of the two time samples done. + // + if (ms<0) ms= 0; + } + + // Unlock and start a wait, atomically (like condition variables do) + // + rc= SignalObjectAndWait( *mu_ref, // "object to signal" (unlock) + *ref, // "object to wait on" + ms, + FALSE ); // not alertable + + // All waiting locks are woken here; each competes for the lock in turn. + // + // Note: We must get the lock even if we've timed out; it makes upper + // level code equivalent to how PThread does it. + // + MUTEX_LOCK(mu_ref); + + if (rc==WAIT_TIMEOUT) return FALSE; + if (rc!=0) FAIL( "SignalObjectAndWait", rc ); + return TRUE; + } + void SIGNAL_ALL( SIGNAL_T *ref ) { +/* + * MSDN tries to scare that 'PulseEvent' is bad, unreliable and should not be + * used. Use condition variables instead (wow, they have that!?!); which will + * ONLY WORK on Vista and 2008 Server, it seems... so MS, isn't it. + * + * I refuse to believe that; using 'PulseEvent' is probably just as good as + * using Windows (XP) in the first place. Just don't use APC's (asynchronous + * process calls) in your C side coding. + */ + // PulseEvent on manual event: + // + // Release ALL threads waiting for it (and go instantly back to unsignalled + // status = future threads to start a wait will wait) + // + if (!PulseEvent( *ref )) + FAIL( "PulseEvent", GetLastError() ); + } +#else + // PThread (Linux, OS X, ...) + // + // On OS X, user processes seem to be able to change priorities. + // On Linux, SCHED_RR and su privileges are required.. !-( + // + #include <errno.h> + #include <sys/time.h> + // + static void _PT_FAIL( int rc, const char *name, const char *file, uint_t line ) { + const char *why= (rc==EINVAL) ? "EINVAL" : + (rc==EBUSY) ? "EBUSY" : + (rc==EPERM) ? "EPERM" : + (rc==ENOMEM) ? "ENOMEM" : + (rc==ESRCH) ? "ESRCH" : + //... + ""; + fprintf( stderr, "%s %d: %s failed, %d %s\n", file, line, name, rc, why ); + abort(); + } + #define PT_CALL( call ) { int rc= call; if (rc!=0) _PT_FAIL( rc, #call, __FILE__, __LINE__ ); } + // + void SIGNAL_INIT( SIGNAL_T *ref ) { + PT_CALL( pthread_cond_init(ref,NULL /*attr*/) ); + } + void SIGNAL_FREE( SIGNAL_T *ref ) { + PT_CALL( pthread_cond_destroy(ref) ); + } + // + /* + * Timeout is given as absolute since we may have fake wakeups during + * a timed out sleep. A Linda with some other key read, or just because + * PThread cond vars can wake up unwantedly. + */ + bool_t SIGNAL_WAIT( SIGNAL_T *ref, pthread_mutex_t *mu, time_d abs_secs ) { + if (abs_secs<0.0) { + PT_CALL( pthread_cond_wait( ref, mu ) ); // infinite + } else { + int rc; + struct timespec ts; + + assert( abs_secs != 0.0 ); + prepare_timeout( &ts, abs_secs ); + + rc= pthread_cond_timedwait( ref, mu, &ts ); + + if (rc==ETIMEDOUT) return FALSE; + if (rc) { _PT_FAIL( rc, "pthread_cond_timedwait()", __FILE__, __LINE__ ); } + } + return TRUE; + } + // + void SIGNAL_ONE( SIGNAL_T *ref ) { + PT_CALL( pthread_cond_signal(ref) ); // wake up ONE (or no) waiting thread + } + // + void SIGNAL_ALL( SIGNAL_T *ref ) { + PT_CALL( pthread_cond_broadcast(ref) ); // wake up ALL waiting threads + } + // + void THREAD_CREATE( THREAD_T* ref, + THREAD_RETURN_T (*func)( void * ), + void *data, int prio /* -2..+2 */ ) { + pthread_attr_t _a; + pthread_attr_t *a= &_a; + struct sched_param sp; + + PT_CALL( pthread_attr_init(a) ); + +#ifndef PTHREAD_TIMEDJOIN + // We create a NON-JOINABLE thread. This is mainly due to the lack of + // 'pthread_timedjoin()', but does offer other benefits (s.a. earlier + // freeing of the thread's resources). + // + PT_CALL( pthread_attr_setdetachstate(a,PTHREAD_CREATE_DETACHED) ); +#endif + + // Use this to find a system's default stack size (DEBUG) +#if 0 + { size_t n; pthread_attr_getstacksize( a, &n ); + fprintf( stderr, "Getstack: %u\n", (unsigned int)n ); } + // 524288 on OS X + // 2097152 on Linux x86 (Ubuntu 7.04) + // 1048576 on FreeBSD 6.2 SMP i386 +#endif + +#if (defined _THREAD_STACK_SIZE) && (_THREAD_STACK_SIZE > 0) + PT_CALL( pthread_attr_setstacksize( a, _THREAD_STACK_SIZE ) ); +#endif + + bool_t normal= +#if defined(PLATFORM_LINUX) && defined(LINUX_SCHED_RR) + !sudo; // with sudo, even normal thread must use SCHED_RR +#else + prio == 0; // create a default thread if +#endif + if (!normal) { + // NB: PThreads priority handling is about as twisty as one can get it + // (and then some). DON*T TRUST ANYTHING YOU READ ON THE NET!!! + + // "The specified scheduling parameters are only used if the scheduling + // parameter inheritance attribute is PTHREAD_EXPLICIT_SCHED." + // + PT_CALL( pthread_attr_setinheritsched( a, PTHREAD_EXPLICIT_SCHED ) ); + + //--- + // "Select the scheduling policy for the thread: one of SCHED_OTHER + // (regular, non-real-time scheduling), SCHED_RR (real-time, + // round-robin) or SCHED_FIFO (real-time, first-in first-out)." + // + // "Using the RR policy ensures that all threads having the same + // priority level will be scheduled equally, regardless of their activity." + // + // "For SCHED_FIFO and SCHED_RR, the only required member of the + // sched_param structure is the priority sched_priority. For SCHED_OTHER, + // the affected scheduling parameters are implementation-defined." + // + // "The priority of a thread is specified as a delta which is added to + // the priority of the process." + // + // ".. priority is an integer value, in the range from 1 to 127. + // 1 is the least-favored priority, 127 is the most-favored." + // + // "Priority level 0 cannot be used: it is reserved for the system." + // + // "When you use specify a priority of -99 in a call to + // pthread_setschedparam(), the priority of the target thread is + // lowered to the lowest possible value." + // + // ... + + // ** CONCLUSION ** + // + // PThread priorities are _hugely_ system specific, and we need at + // least OS specific settings. Hopefully, Linuxes and OS X versions + // are uniform enough, among each other... + // +#ifdef PLATFORM_OSX + // AK 10-Apr-07 (OS X PowerPC 10.4.9): + // + // With SCHED_RR, 26 seems to be the "normal" priority, where setting + // it does not seem to affect the order of threads processed. + // + // With SCHED_OTHER, the range 25..32 is normal (maybe the same 26, + // but the difference is not so clear with OTHER). + // + // 'sched_get_priority_min()' and '..max()' give 15, 47 as the + // priority limits. This could imply, user mode applications won't + // be able to use values outside of that range. + // + #define _PRIO_MODE SCHED_OTHER + + // OS X 10.4.9 (PowerPC) gives ENOTSUP for process scope + //#define _PRIO_SCOPE PTHREAD_SCOPE_PROCESS + + #define _PRIO_HI 32 // seems to work (_carefully_ picked!) + #define _PRIO_0 26 // detected + #define _PRIO_LO 1 // seems to work (tested) + +#elif defined(PLATFORM_LINUX) + // (based on Ubuntu Linux 2.6.15 kernel) + // + // SCHED_OTHER is the default policy, but does not allow for priorities. + // SCHED_RR allows priorities, all of which (1..99) are higher than + // a thread with SCHED_OTHER policy. + // + // <http://kerneltrap.org/node/6080> + // <http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library> + // <http://www.net.in.tum.de/~gregor/docs/pthread-scheduling.html> + // + // Manuals suggest checking #ifdef _POSIX_THREAD_PRIORITY_SCHEDULING, + // but even Ubuntu does not seem to define it. + // + #define _PRIO_MODE SCHED_RR + + // NTLP 2.5: only system scope allowed (being the basic reason why + // root privileges are required..) + //#define _PRIO_SCOPE PTHREAD_SCOPE_PROCESS + + #define _PRIO_HI 99 + #define _PRIO_0 50 + #define _PRIO_LO 1 + +#elif defined(PLATFORM_BSD) + // + // <http://www.net.in.tum.de/~gregor/docs/pthread-scheduling.html> + // + // "When control over the thread scheduling is desired, then FreeBSD + // with the libpthread implementation is by far the best choice .." + // + #define _PRIO_MODE SCHED_OTHER + #define _PRIO_SCOPE PTHREAD_SCOPE_PROCESS + #define _PRIO_HI 31 + #define _PRIO_0 15 + #define _PRIO_LO 1 + +#elif defined(PLATFORM_CYGWIN) + // + // TBD: Find right values for Cygwin + // +#else + #error "Unknown OS: not implemented!" +#endif + +#ifdef _PRIO_SCOPE + PT_CALL( pthread_attr_setscope( a, _PRIO_SCOPE ) ); +#endif + PT_CALL( pthread_attr_setschedpolicy( a, _PRIO_MODE ) ); + +#define _PRIO_AN (_PRIO_0 + ((_PRIO_HI-_PRIO_0)/2) ) +#define _PRIO_BN (_PRIO_LO + ((_PRIO_0-_PRIO_LO)/2) ) + + sp.sched_priority= + (prio == +2) ? _PRIO_HI : + (prio == +1) ? _PRIO_AN : +#if defined(PLATFORM_LINUX) && defined(LINUX_SCHED_RR) + (prio == 0) ? _PRIO_0 : +#endif + (prio == -1) ? _PRIO_BN : _PRIO_LO; + + PT_CALL( pthread_attr_setschedparam( a, &sp ) ); + } + + //--- + // Seems on OS X, _POSIX_THREAD_THREADS_MAX is some kind of system + // thread limit (not userland thread). Actual limit for us is way higher. + // PTHREAD_THREADS_MAX is not defined (even though man page refers to it!) + // +# ifndef THREAD_CREATE_RETRIES_MAX + // Don't bother with retries; a failure is a failure + // + { + int rc= pthread_create( ref, a, func, data ); + if (rc) _PT_FAIL( rc, "pthread_create()", __FILE__, __LINE__-1 ); + } +# else +# error "This code deprecated" +/* + // Wait slightly if thread creation has exchausted the system + // + { uint_t retries; + for( retries=0; retries<THREAD_CREATE_RETRIES_MAX; retries++ ) { + + int rc= pthread_create( ref, a, func, data ); + // + // OS X / Linux: + // EAGAIN: ".. lacked the necessary resources to create + // another thread, or the system-imposed limit on the + // total number of threads in a process + // [PTHREAD_THREADS_MAX] would be exceeded." + // EINVAL: attr is invalid + // Linux: + // EPERM: no rights for given parameters or scheduling (no sudo) + // ENOMEM: (known to fail with this code, too - not listed in man) + + if (rc==0) break; // ok! + + // In practise, exhaustion seems to be coming from memory, not a + // maximum number of threads. Keep tuning... ;) + // + if (rc==EAGAIN) { +//fprintf( stderr, "Looping (retries=%d) ", retries ); // DEBUG + + // Try again, later. + + Yield(); + } else { + _PT_FAIL( rc, "pthread_create()", __FILE__, __LINE__ ); + } + } + } +*/ +# endif + + if (a) { + PT_CALL( pthread_attr_destroy(a) ); + } + } + // + /* + * Wait for a thread to finish. + * + * 'mu_ref' is a lock we should use for the waiting; initially unlocked. + * Same lock as passed to THREAD_EXIT. + * + * Returns TRUE for succesful wait, FALSE for timed out + */ +#ifdef PTHREAD_TIMEDJOIN + bool_t THREAD_WAIT( THREAD_T *ref, double secs ) +#else + bool_t THREAD_WAIT( THREAD_T *ref, SIGNAL_T *signal_ref, MUTEX_T *mu_ref, volatile enum e_status *st_ref, double secs ) +#endif +{ + struct timespec ts_store; + const struct timespec *timeout= NULL; + bool_t done; + + // Do timeout counting before the locks + // +#ifdef PTHREAD_TIMEDJOIN + if (secs>=0.0) { +#else + if (secs>0.0) { +#endif + prepare_timeout( &ts_store, now_secs()+secs ); + timeout= &ts_store; + } + +#ifdef PTHREAD_TIMEDJOIN + /* Thread is joinable + */ + if (!timeout) { + PT_CALL( pthread_join( *ref, NULL /*ignore exit value*/ )); + done= TRUE; + } else { + int rc= PTHREAD_TIMEDJOIN( *ref, NULL, timeout ); + if ((rc!=0) && (rc!=ETIMEDOUT)) { + _PT_FAIL( rc, "PTHREAD_TIMEDJOIN", __FILE__, __LINE__-2 ); + } + done= rc==0; + } +#else + /* Since we've set the thread up as PTHREAD_CREATE_DETACHED, we cannot + * join with it. Use the cond.var. + */ + MUTEX_LOCK( mu_ref ); + + // 'secs'==0.0 does not need to wait, just take the current status + // within the 'mu_ref' locks + // + if (secs != 0.0) { + while( *st_ref < DONE ) { + if (!timeout) { + PT_CALL( pthread_cond_wait( signal_ref, mu_ref )); + } else { + int rc= pthread_cond_timedwait( signal_ref, mu_ref, timeout ); + if (rc==ETIMEDOUT) break; + if (rc!=0) _PT_FAIL( rc, "pthread_cond_timedwait", __FILE__, __LINE__-2 ); + } + } + } + done= *st_ref >= DONE; // DONE|ERROR_ST|CANCELLED + + MUTEX_UNLOCK( mu_ref ); +#endif + return done; + } + // + void THREAD_KILL( THREAD_T *ref ) { + pthread_cancel( *ref ); + } +#endif + +static const lua_Alloc alloc_f= 0; diff --git a/src/lualanes/threading.h b/src/lualanes/threading.h new file mode 100644 index 0000000000000000000000000000000000000000..4a83229b64f373fd9d253315ffe71a64438d60e5 --- /dev/null +++ b/src/lualanes/threading.h @@ -0,0 +1,196 @@ +/* +* THREADING.H +*/ +#ifndef THREADING_H +#define THREADING_H + +/* Platform detection +*/ +#ifdef _WIN32_WCE + #define PLATFORM_POCKETPC +#elif (defined _WIN32) + #define PLATFORM_WIN32 +#elif (defined __linux__) + #define PLATFORM_LINUX +#elif (defined __APPLE__) && (defined __MACH__) + #define PLATFORM_OSX +#elif (defined __NetBSD__) || (defined __FreeBSD__) || (defined BSD) + #define PLATFORM_BSD +#elif (defined __QNX__) + #define PLATFORM_QNX +#elif (defined __CYGWIN__) + #define PLATFORM_CYGWIN +#else + #error "Unknown platform!" +#endif + +typedef int bool_t; +#ifndef FALSE +# define FALSE 0 +# define TRUE 1 +#endif + +typedef unsigned int uint_t; + +#if defined(PLATFORM_WIN32) && defined(__GNUC__) +/* MinGW with MSVCR80.DLL */ +/* Do this BEFORE including time.h so that it is declaring _mktime32() + * as it would have declared mktime(). + */ +# define mktime _mktime32 +#endif +#include <time.h> + +/* Note: ERROR is a defined entity on Win32 +*/ +enum e_status { PENDING, RUNNING, WAITING, DONE, ERROR_ST, CANCELLED }; + + +/*---=== Locks & Signals ===--- +*/ + +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) + #define WIN32_LEAN_AND_MEAN + // 'SignalObjectAndWait' needs this (targets Windows 2000 and above) + #define _WIN32_WINNT 0x0400 + #include <windows.h> + #include <process.h> + + // 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. + // + #define MUTEX_T HANDLE + void MUTEX_INIT( MUTEX_T *ref ); + #define MUTEX_RECURSIVE_INIT(ref) MUTEX_INIT(ref) /* always recursive in Win32 */ + void MUTEX_FREE( MUTEX_T *ref ); + void MUTEX_LOCK( MUTEX_T *ref ); + void MUTEX_UNLOCK( MUTEX_T *ref ); + + typedef unsigned THREAD_RETURN_T; + + #define SIGNAL_T HANDLE + + #define YIELD() Sleep(0) +#else + // PThread (Linux, OS X, ...) + // + #include <pthread.h> + + #ifdef PLATFORM_LINUX + # define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP + #else + /* OS X, ... */ + # define _MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE + #endif + + #define MUTEX_T pthread_mutex_t + #define MUTEX_INIT(ref) pthread_mutex_init(ref,NULL) + #define MUTEX_RECURSIVE_INIT(ref) \ + { pthread_mutexattr_t a; pthread_mutexattr_init( &a ); \ + pthread_mutexattr_settype( &a, _MUTEX_RECURSIVE ); \ + pthread_mutex_init(ref,&a); pthread_mutexattr_destroy( &a ); \ + } + #define MUTEX_FREE(ref) pthread_mutex_destroy(ref) + #define MUTEX_LOCK(ref) pthread_mutex_lock(ref) + #define MUTEX_UNLOCK(ref) pthread_mutex_unlock(ref) + + typedef void * THREAD_RETURN_T; + + typedef pthread_cond_t SIGNAL_T; + + void SIGNAL_ONE( SIGNAL_T *ref ); + + // Yield is non-portable: + // + // OS X 10.4.8/9 has pthread_yield_np() + // Linux 2.4 has pthread_yield() if _GNU_SOURCE is #defined + // FreeBSD 6.2 has pthread_yield() + // ... + // + #ifdef PLATFORM_OSX + #define YIELD() pthread_yield_np() + #else + #define YIELD() pthread_yield() + #endif +#endif + +void SIGNAL_INIT( SIGNAL_T *ref ); +void SIGNAL_FREE( SIGNAL_T *ref ); +void SIGNAL_ALL( SIGNAL_T *ref ); + +/* +* 'time_d': <0.0 for no timeout +* 0.0 for instant check +* >0.0 absolute timeout in secs + ms +*/ +typedef double time_d; +time_d now_secs(void); + +time_d SIGNAL_TIMEOUT_PREPARE( double rel_secs ); + +bool_t SIGNAL_WAIT( SIGNAL_T *ref, MUTEX_T *mu, time_d timeout ); + + +/*---=== Threading ===--- +*/ + +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) + + typedef HANDLE THREAD_T; + // + void THREAD_CREATE( THREAD_T *ref, + THREAD_RETURN_T (__stdcall *func)( void * ), + void *data, int prio /* -3..+3 */ ); + +# define THREAD_PRIO_MIN (-3) +# define THREAD_PRIO_MAX (+3) + +#else + /* Platforms that have a timed 'pthread_join()' can get away with a simpler + * implementation. Others will use a condition variable. + */ +# ifdef USE_PTHREAD_TIMEDJOIN +# ifdef PLATFORM_OSX +# error "No 'pthread_timedjoin()' on this system" +# else + /* Linux, ... */ +# define PTHREAD_TIMEDJOIN pthread_timedjoin_np +# endif +# endif + + typedef pthread_t THREAD_T; + + void THREAD_CREATE( THREAD_T *ref, + THREAD_RETURN_T (*func)( void * ), + void *data, int prio /* -2..+2 */ ); + +# if defined(PLATFORM_LINUX) + volatile bool_t sudo; +# ifdef LINUX_SCHED_RR +# define THREAD_PRIO_MIN (sudo ? -2 : 0) +# else +# define THREAD_PRIO_MIN (0) +# endif +# define THREAD_PRIO_MAX (sudo ? +2 : 0) +# else +# define THREAD_PRIO_MIN (-2) +# define THREAD_PRIO_MAX (+2) +# endif +#endif + +/* +* Win32 and PTHREAD_TIMEDJOIN allow waiting for a thread with a timeout. +* Posix without PTHREAD_TIMEDJOIN needs to use a condition variable approach. +*/ +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) + bool_t THREAD_WAIT( THREAD_T *ref, double secs ); +#else + bool_t THREAD_WAIT( THREAD_T *ref, SIGNAL_T *signal_ref, MUTEX_T *mu_ref, volatile enum e_status *st_ref, double secs ); +#endif + +void THREAD_KILL( THREAD_T *ref ); + +#endif + // THREADING_H diff --git a/src/lualanes/tools.c b/src/lualanes/tools.c new file mode 100644 index 0000000000000000000000000000000000000000..9ff68c278dd691afe13f0d18f1e5c286debe1123 --- /dev/null +++ b/src/lualanes/tools.c @@ -0,0 +1,1208 @@ +/* + * TOOLS.C Copyright (c) 2002-08, Asko Kauppi + * + * Lua tools to support Lanes. +*/ + +/* +=============================================================================== + +Copyright (C) 2002-08 Asko Kauppi <akauppi@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 +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +=============================================================================== +*/ + +#include "tools.h" + +#include "lualib.h" +#include "lauxlib.h" + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> + +static volatile lua_CFunction hijacked_tostring; // = NULL + +MUTEX_T deep_lock; +MUTEX_T mtid_lock; + +/*---=== luaG_dump ===---*/ + +void luaG_dump( lua_State* L ) { + + int top= lua_gettop(L); + int i; + + fprintf( stderr, "\n\tDEBUG STACK:\n" ); + + if (top==0) + fprintf( stderr, "\t(none)\n" ); + + for( i=1; i<=top; i++ ) { + int type= lua_type( L, i ); + + fprintf( stderr, "\t[%d]= (%s) ", i, lua_typename(L,type) ); + + // Print item contents here... + // + // Note: this requires 'tostring()' to be defined. If it is NOT, + // enable it for more debugging. + // + STACK_CHECK(L) + STACK_GROW( L, 2 ) + + lua_getglobal( L, "tostring" ); + // + // [-1]: tostring function, or nil + + if (!lua_isfunction(L,-1)) { + fprintf( stderr, "('tostring' not available)" ); + } else { + lua_pushvalue( L, i ); + lua_call( L, 1 /*args*/, 1 /*retvals*/ ); + + // Don't trust the string contents + // + fprintf( stderr, "%s", lua_tostring(L,-1) ); + } + lua_pop(L,1); + STACK_END(L,0) + fprintf( stderr, "\n" ); + } + fprintf( stderr, "\n" ); +} + + +/*---=== luaG_openlibs ===---*/ +extern int luaopen_socket_core(lua_State *L); +extern int luaopen_mime_core(lua_State *L); +extern int luaopen_struct(lua_State *L); +extern int luaopen_profiler(lua_State *L); + +static const luaL_Reg libs[] = { + { LUA_LOADLIBNAME, luaopen_package }, + { LUA_TABLIBNAME, luaopen_table }, + { LUA_IOLIBNAME, luaopen_io }, + { LUA_OSLIBNAME, luaopen_os }, + { LUA_STRLIBNAME, luaopen_string }, + { LUA_MATHLIBNAME, luaopen_math }, + { LUA_DBLIBNAME, luaopen_debug }, + // + { "base", NULL }, // ignore "base" (already acquired it) + { "coroutine", NULL }, // part of Lua 5.1 base package + + // T-Engine embeded libs + { "socket.core", luaopen_socket_core }, + { "mime.core", luaopen_mime_core }, + { "struct", luaopen_struct }, + { "profiler", luaopen_profiler }, + { NULL, NULL } +}; + +static bool_t openlib( lua_State *L, const char *name, size_t len ) { + + unsigned i; + bool_t all= strncmp( name, "*", len ) == 0; + + for( i=0; libs[i].name; i++ ) { + if (all || (strncmp(name, libs[i].name, len) ==0)) { + if (libs[i].func) { + STACK_GROW(L,2); + lua_pushcfunction( L, libs[i].func ); + lua_pushstring( L, libs[i].name ); + lua_call( L, 1, 0 ); + } + if (!all) return TRUE; + } + } + return all; +} + +/* +* Like 'luaL_openlibs()' but allows the set of libraries be selected +* +* NULL no libraries, not even base +* "" base library only +* "io,string" named libraries +* "*" all libraries +* +* Base ("unpack", "print" etc.) is always added, unless 'libs' is NULL. +* +* Returns NULL for ok, position of error within 'libs' on failure. +*/ +#define is_name_char(c) (isalpha(c) || (c)=='*') + +const char *luaG_openlibs( lua_State *L, const char *libs ) { + const char *p; + unsigned len; + + if (!libs) return NULL; // no libs, not even 'base' + + // 'lua.c' stops GC during initialization so perhaps its a good idea. :) + // + lua_gc(L, LUA_GCSTOP, 0); + + // Anything causes 'base' to be taken in + // + STACK_GROW(L,2); + lua_pushcfunction( L, luaopen_base ); + lua_pushliteral( L, "" ); + lua_call( L, 1, 0 ); + + for( p= libs; *p; p+=len ) { + len=0; + while (*p && !is_name_char(*p)) p++; // bypass delimiters + while (is_name_char(p[len])) len++; // bypass name + if (len && (!openlib( L, p, len ))) + break; + } + lua_gc(L, LUA_GCRESTART, 0); + + return *p ? p : NULL; +} + + + +/*---=== Deep userdata ===---*/ + +/* The deep portion must be allocated separately of any Lua state's; it's +* lifespan may be longer than that of the creating state. +*/ +#define DEEP_MALLOC malloc +#define DEEP_FREE free + +/* +* 'registry[REGKEY]' is a two-way lookup table for 'idfunc's and those type's +* metatables: +* +* metatable -> idfunc +* idfunc -> metatable +*/ +#define DEEP_LOOKUP_KEY ((void*)set_deep_lookup) + // any unique light userdata + +static void push_registry_subtable( lua_State *L, void *token ); + +/* +* Sets up [-1]<->[-2] two-way lookups, and ensures the lookup table exists. +* Pops the both values off the stack. +*/ +void set_deep_lookup( lua_State *L ) { + + STACK_GROW(L,3); + + STACK_CHECK(L) +#if 1 + push_registry_subtable( L, DEEP_LOOKUP_KEY ); +#else + /* ..to be removed.. */ + lua_pushlightuserdata( L, DEEP_LOOKUP_KEY ); + lua_rawget( L, LUA_REGISTRYINDEX ); + + if (lua_isnil(L,-1)) { + // First time here; let's make the lookup + // + lua_pop(L,1); + + lua_newtable(L); + lua_pushlightuserdata( L, DEEP_LOOKUP_KEY ); + lua_pushvalue(L,-2); + // + // [-3]: {}Â (2nd ref) + // [-2]: DEEP_LOOKUP_KEY + // [-1]: {} + + lua_rawset( L, LUA_REGISTRYINDEX ); + // + // [-1]: lookup table (empty) + } +#endif + STACK_MID(L,1) + + lua_insert(L,-3); + + // [-3]: lookup table + // [-2]: A + // [-1]: B + + lua_pushvalue( L,-1 ); // B + lua_pushvalue( L,-3 ); // A + lua_rawset( L, -5 ); // B->A + lua_rawset( L, -3 ); // A->B + lua_pop( L,1 ); + + STACK_END(L,-2) +} + +/* +* Pops the key (metatable or idfunc) off the stack, and replaces with the +* deep lookup value (idfunc/metatable/nil). +*/ +void get_deep_lookup( lua_State *L ) { + + STACK_GROW(L,1); + + STACK_CHECK(L) + lua_pushlightuserdata( L, DEEP_LOOKUP_KEY ); + lua_rawget( L, LUA_REGISTRYINDEX ); + + if (!lua_isnil(L,-1)) { + // [-2]: key (metatable or idfunc) + // [-1]: lookup table + + lua_insert( L, -2 ); + lua_rawget( L, -2 ); + + // [-2]: lookup table + // [-1]: value (metatable / idfunc / nil) + } + lua_remove(L,-2); + // remove lookup, or unused key + STACK_END(L,0) +} + +/* +* Return the registered ID function for 'index' (deep userdata proxy), +* or NULL if 'index' is not a deep userdata proxy. +*/ +static +lua_CFunction get_idfunc( lua_State *L, int index ) { + lua_CFunction ret; + + index= STACK_ABS(L,index); + + STACK_GROW(L,1); + + STACK_CHECK(L) + if (!lua_getmetatable( L, index )) + return NULL; // no metatable + + // [-1]: metatable of [index] + + get_deep_lookup(L); + // + // [-1]: idfunc/nil + + ret= lua_tocfunction(L,-1); + lua_pop(L,1); + STACK_END(L,0) + return ret; +} + + +/* +* void= mt.__gc( proxy_ud ) +* +* End of life for a proxy object; reduce the deep reference count and clean +* it up if reaches 0. +*/ +static +int deep_userdata_gc( lua_State *L ) { + DEEP_PRELUDE **proxy= (DEEP_PRELUDE**)lua_touserdata( L, 1 ); + DEEP_PRELUDE *p= *proxy; + int v; + + *proxy= 0; // make sure we don't use it any more + + MUTEX_LOCK( &deep_lock ); + v= --(p->refcount); + MUTEX_UNLOCK( &deep_lock ); + + if (v==0) { + int pushed; + + // Call 'idfunc( "delete", deep_ptr )' to make deep cleanup + // + lua_CFunction idfunc= get_idfunc(L,1); + ASSERT_L(idfunc); + + lua_settop(L,0); // clean stack so we can call 'idfunc' directly + + // void= idfunc( "delete", lightuserdata ) + // + lua_pushliteral( L, "delete" ); + lua_pushlightuserdata( L, p->deep ); + pushed= idfunc(L); + + if (pushed) + luaL_error( L, "Bad idfunc on \"delete\": returned something" ); + + DEEP_FREE( (void*)p ); + } + return 0; +} + + +/* +* Push a proxy userdata on the stack. +* +* Initializes necessary structures if it's the first time 'idfunc' is being +* used in this Lua state (metatable, registring it). Otherwise, increments the +* reference count. +*/ +void luaG_push_proxy( lua_State *L, lua_CFunction idfunc, DEEP_PRELUDE *prelude ) { + DEEP_PRELUDE **proxy; + + MUTEX_LOCK( &deep_lock ); + ++(prelude->refcount); // one more proxy pointing to this deep data + MUTEX_UNLOCK( &deep_lock ); + + STACK_GROW(L,4); + + STACK_CHECK(L) + + proxy= lua_newuserdata( L, sizeof( DEEP_PRELUDE* ) ); + ASSERT_L(proxy); + *proxy= prelude; + + // Get/create metatable for 'idfunc' (in this state) + // + lua_pushcfunction( L, idfunc ); // key + get_deep_lookup(L); + // + // [-2]: proxy + // [-1]: metatable / nil + + if (lua_isnil(L,-1)) { + // No metatable yet; make one and register it + // + lua_pop(L,1); + + // tbl= idfunc( "metatable" ) + // + lua_pushcfunction( L, idfunc ); + lua_pushliteral( L, "metatable" ); + lua_call( L, 1 /*args*/, 1 /*results*/ ); + // + // [-2]: proxy + // [-1]: metatable (returned by 'idfunc') + + if (!lua_istable(L,-1)) + luaL_error( L, "Bad idfunc on \"metatable\": did not return one" ); + + // Add '__gc' method + // + lua_pushcfunction( L, deep_userdata_gc ); + lua_setfield( L, -2, "__gc" ); + + // Memorize for later rounds + // + lua_pushvalue( L,-1 ); + lua_pushcfunction( L, idfunc ); + // + // [-4]: proxy + // [-3]: metatable (2nd ref) + // [-2]: metatable + // [-1]: idfunc + + set_deep_lookup(L); + } + STACK_MID(L,2) + ASSERT_L( lua_isuserdata(L,-2) ); + ASSERT_L( lua_istable(L,-1) ); + + // [-2]: proxy userdata + // [-1]: metatable to use + + lua_setmetatable( L, -2 ); + + STACK_END(L,1) + // [-1]: proxy userdata +} + + +/* +* Create a deep userdata +* +* proxy_ud= deep_userdata( idfunc [, ...] ) +* +* Creates a deep userdata entry of the type defined by 'idfunc'. +* Other parameters are passed on to the 'idfunc' "new" invocation. +* +* 'idfunc' must fulfill the following features: +* +* lightuserdata= idfunc( "new" [, ...] ) -- creates a new deep data instance +* void= idfunc( "delete", lightuserdata ) -- releases a deep data instance +* tbl= idfunc( "metatable" ) -- gives metatable for userdata proxies +* +* Reference counting and true userdata proxying are taken care of for the +* actual data type. +* +* Types using the deep userdata system (and only those!) can be passed between +* separate Lua states via 'luaG_inter_move()'. +* +* Returns: 'proxy' userdata for accessing the deep data via 'luaG_todeep()' +*/ +int luaG_deep_userdata( lua_State *L ) { + lua_CFunction idfunc= lua_tocfunction( L,1 ); + int pushed; + + DEEP_PRELUDE *prelude= DEEP_MALLOC( sizeof(DEEP_PRELUDE) ); + ASSERT_L(prelude); + + prelude->refcount= 0; // 'luaG_push_proxy' will lift it to 1 + + STACK_GROW(L,1); + STACK_CHECK(L) + + // Replace 'idfunc' with "new" in the stack (keep possible other params) + // + lua_remove(L,1); + lua_pushliteral( L, "new" ); + lua_insert(L,1); + + // lightuserdata= idfunc( "new" [, ...] ) + // + pushed= idfunc(L); + + if ((pushed!=1) || lua_type(L,-1) != LUA_TLIGHTUSERDATA) + luaL_error( L, "Bad idfunc on \"new\": did not return light userdata" ); + + prelude->deep= lua_touserdata(L,-1); + ASSERT_L(prelude->deep); + + lua_pop(L,1); // pop deep data + + luaG_push_proxy( L, idfunc, prelude ); + // + // [-1]: proxy userdata + + STACK_END(L,1) + return 1; +} + + +/* +* Access deep userdata through a proxy. +* +* Reference count is not changed, and access to the deep userdata is not +* serialized. It is the module's responsibility to prevent conflicting usage. +*/ +void *luaG_todeep( lua_State *L, lua_CFunction idfunc, int index ) { + DEEP_PRELUDE **proxy; + + STACK_CHECK(L) + if (get_idfunc(L,index) != idfunc) + return NULL; // no metatable, or wrong kind + + proxy= (DEEP_PRELUDE**)lua_touserdata( L, index ); + STACK_END(L,0) + + return (*proxy)->deep; +} + + +/* +* Copy deep userdata between two separate Lua states. +* +* Returns: +* the id function of the copied value, or NULL for non-deep userdata +* (not copied) +*/ +static +lua_CFunction luaG_copydeep( lua_State *L, lua_State *L2, int index ) { + DEEP_PRELUDE **proxy; + DEEP_PRELUDE *p; + + lua_CFunction idfunc; + + idfunc= get_idfunc( L, index ); + if (!idfunc) return NULL; // not a deep userdata + + // Increment reference count + // + proxy= (DEEP_PRELUDE**)lua_touserdata( L, index ); + p= *proxy; + + luaG_push_proxy( L2, idfunc, p ); + // + // L2 [-1]: proxy userdata + + return idfunc; +} + + + +/*---=== Inter-state copying ===---*/ + +/*-- Metatable copying --*/ + +/* + * 'reg[ REG_MT_KNOWN ]'= { + * [Â table ]= id_uint, + * ... + * [ id_uint ]= table, + * ... + * } + */ + +/* +* Push a registry subtable (keyed by unique 'token') onto the stack. +* If the subtable does not exist, it is created and chained. +*/ +static +void push_registry_subtable( lua_State *L, void *token ) { + + STACK_GROW(L,3); + + STACK_CHECK(L) + + lua_pushlightuserdata( L, token ); + lua_rawget( L, LUA_REGISTRYINDEX ); + // + // [-1]: nil/subtable + + if (lua_isnil(L,-1)) { + lua_pop(L,1); + lua_newtable(L); // value + lua_pushlightuserdata( L, token ); // key + lua_pushvalue(L,-2); + // + // [-3]: value (2nd ref) + // [-2]: key + // [-1]: value + + lua_rawset( L, LUA_REGISTRYINDEX ); + } + STACK_END(L,1) + + ASSERT_L( lua_istable(L,-1) ); +} + +#define REG_MTID ( (void*) get_mt_id ) + +/* +* Get a unique ID for metatable at [i]. +*/ +static +uint_t get_mt_id( lua_State *L, int i ) { + static uint_t last_id= 0; + uint_t id; + + i= STACK_ABS(L,i); + + STACK_GROW(L,3); + + STACK_CHECK(L) + push_registry_subtable( L, REG_MTID ); + lua_pushvalue(L, i); + lua_rawget( L, -2 ); + // + // [-2]: reg[REG_MTID] + // [-1]: nil/uint + + id= lua_tointeger(L,-1); // 0 for nil + lua_pop(L,1); + STACK_MID(L,1) + + if (id==0) { + MUTEX_LOCK( &mtid_lock ); + id= ++last_id; + MUTEX_UNLOCK( &mtid_lock ); + + /* Create two-way references: id_uint <-> table + */ + lua_pushvalue(L,i); + lua_pushinteger(L,id); + lua_rawset( L, -3 ); + + lua_pushinteger(L,id); + lua_pushvalue(L,i); + lua_rawset( L, -3 ); + } + lua_pop(L,1); // remove 'reg[REG_MTID]' reference + + STACK_END(L,0) + + return id; +} + + +static int buf_writer( lua_State *L, const void* b, size_t n, void* B ) { + (void)L; + luaL_addlstring((luaL_Buffer*) B, (const char *)b, n); + return 0; +} + + +/* + * Check if we've already copied the same table from 'L', and + * reuse the old copy. This allows table upvalues shared by multiple + * local functions to point to the same table, also in the target. + * + * Always pushes a table to 'L2'. + * + * Returns TRUE if the table was cached (no need to fill it!); FALSE if + * it's a virgin. + */ +static +bool_t push_cached_table( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ) { + bool_t ret; + + ASSERT_L( hijacked_tostring ); + ASSERT_L( L2_cache_i != 0 ); + + STACK_GROW(L,2); + STACK_GROW(L2,3); + + // Create an identity string for table at [i]; it should stay unique at + // least during copying of the data (then we can clear the caches). + // + STACK_CHECK(L) + lua_pushcfunction( L, hijacked_tostring ); + lua_pushvalue( L, i ); + lua_call( L, 1 /*args*/, 1 /*retvals*/ ); + // + // [-1]: "table: 0x...." + + STACK_END(L,1) + ASSERT_L( lua_type(L,-1) == LUA_TSTRING ); + + // L2_cache[id_str]= [{...}] + // + STACK_CHECK(L2) + + // We don't need to use the from state ('L') in ID since the life span + // is only for the duration of a copy (both states are locked). + // + lua_pushstring( L2, lua_tostring(L,-1) ); + lua_pop(L,1); // remove the 'tostring(tbl)' value (in L!) + +//fprintf( stderr, "<< ID: %s >>\n", lua_tostring(L2,-1) ); + + lua_pushvalue( L2, -1 ); + lua_rawget( L2, L2_cache_i ); + // + // [-2]: identity string ("table: 0x...") + // [-1]: table|nil + + if (lua_isnil(L2,-1)) { + lua_pop(L2,1); + lua_newtable(L2); + lua_pushvalue(L2,-1); + lua_insert(L2,-3); + // + // [-3]: new table (2nd ref) + // [-2]: identity string + // [-1]: new table + + lua_rawset(L2, L2_cache_i); + // + // [-1]: new table (tied to 'L2_cache' table') + + ret= FALSE; // brand new + + } else { + lua_remove(L2,-2); + ret= TRUE; // from cache + } + STACK_END(L2,1) + // + // L2 [-1]: table to use as destination + + ASSERT_L( lua_istable(L2,-1) ); + return ret; +} + + +/* + * Check if we've already copied the same function from 'L', and reuse the old + * copy. + * + * Always pushes a function to 'L2'. + */ +static void inter_copy_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ); + +static +void push_cached_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ) { + // TBD: Merge this and same code for tables + + ASSERT_L( hijacked_tostring ); + ASSERT_L( L2_cache_i != 0 ); + + STACK_GROW(L,2); + STACK_GROW(L2,3); + + STACK_CHECK(L) + lua_pushcfunction( L, hijacked_tostring ); + lua_pushvalue( L, i ); + lua_call( L, 1 /*args*/, 1 /*retvals*/ ); + // + // [-1]: "function: 0x...." + + STACK_END(L,1) + ASSERT_L( lua_type(L,-1) == LUA_TSTRING ); + + // L2_cache[id_str]= function + // + STACK_CHECK(L2) + + // We don't need to use the from state ('L') in ID since the life span + // is only for the duration of a copy (both states are locked). + // + lua_pushstring( L2, lua_tostring(L,-1) ); + lua_pop(L,1); // remove the 'tostring(tbl)' value (in L!) + +//fprintf( stderr, "<< ID: %s >>\n", lua_tostring(L2,-1) ); + + lua_pushvalue( L2, -1 ); + lua_rawget( L2, L2_cache_i ); + // + // [-2]: identity string ("function: 0x...") + // [-1]: function|nil|true (true means: we're working on it; recursive) + + if (lua_isnil(L2,-1)) { + lua_pop(L2,1); + + // Set to 'true' for the duration of creation; need to find self-references + // via upvalues + // + lua_pushboolean(L2,TRUE); + lua_setfield( L2, L2_cache_i, lua_tostring(L2,-2) ); + + inter_copy_func( L2, L2_cache_i, L, i ); // pushes a copy of the func + + lua_pushvalue(L2,-1); + lua_insert(L2,-3); + // + // [-3]: function (2nd ref) + // [-2]: identity string + // [-1]: function + + lua_rawset(L2,L2_cache_i); + // + // [-1]: function (tied to 'L2_cache' table') + + } else if (lua_isboolean(L2,-1)) { + // Loop in preparing upvalues; either direct or via a table + // + // Note: This excludes the case where a function directly addresses + // itself as an upvalue (recursive lane creation). + // + luaL_error( L, "Recursive use of upvalues; cannot copy the function" ); + + } else { + lua_remove(L2,-2); + } + STACK_END(L2,1) + // + // L2 [-1]: function + + ASSERT_L( lua_isfunction(L2,-1) ); +} + + +/* +* Copy a function over, which has not been found in the cache. +*/ +enum e_vt { + VT_NORMAL, VT_KEY, VT_METATABLE +}; +static bool_t inter_copy_one_( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i, enum e_vt value_type ); + +static void inter_copy_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ) { + + lua_CFunction cfunc= lua_tocfunction( L,i ); + unsigned n; + + ASSERT_L( L2_cache_i != 0 ); + + STACK_GROW(L,2); + + STACK_CHECK(L) + if (!cfunc) { // Lua function + luaL_Buffer b; + const char *s; + size_t sz; + int tmp; + const char *name= NULL; + +#if 0 + // "To get information about a function you push it onto the + // stack and start the what string with the character '>'." + // + { lua_Debug ar; + lua_pushvalue( L, i ); + lua_getinfo(L, ">n", &ar); // fills 'name' and 'namewhat', pops function + name= ar.namewhat; + + fprintf( stderr, "NAME: %s\n", name ); // just gives NULL + } +#endif + // 'lua_dump()' needs the function at top of stack + // + if (i!=-1) lua_pushvalue( L, i ); + + luaL_buffinit(L,&b); + tmp= lua_dump(L, buf_writer, &b); + ASSERT_L(tmp==0); + // + // "value returned is the error code returned by the last call + // to the writer" (and we only return 0) + + luaL_pushresult(&b); // pushes dumped string on 'L' + s= lua_tolstring(L,-1,&sz); + ASSERT_L( s && sz ); + + if (i!=-1) lua_remove( L, -2 ); + + // Note: Line numbers seem to be taken precisely from the + // original function. 'name' is not used since the chunk + // is precompiled (it seems...). + // + // TBD: Can we get the function's original name through, as well? + // + if (luaL_loadbuffer(L2, s, sz, name) != 0) { + // chunk is precompiled so only LUA_ERRMEM can happen + // "Otherwise, it pushes an error message" + // + STACK_GROW( L,1 ); + luaL_error( L, "%s", lua_tostring(L2,-1) ); + } + lua_pop(L,1); // remove the dumped string + STACK_MID(L,0) + } + + /* push over any upvalues; references to this function will come from + * cache so we don't end up in eternal loop. + */ + for( n=0; lua_getupvalue( L, i, 1+n ) != NULL; n++ ) { + if ((!cfunc) && lua_equal(L,i,-1)) { + /* Lua closure that has a (recursive) upvalue to itself + */ + lua_pushvalue( L2, -((int)n)-1 ); + } else { + if (!inter_copy_one_( L2, L2_cache_i, L, lua_gettop(L), VT_NORMAL )) + luaL_error( L, "Cannot copy upvalue type '%s'", luaG_typename(L,-1) ); + } + lua_pop(L,1); + } + // L2: function + 'n' upvalues (>=0) + + STACK_MID(L,0) + + if (cfunc) { + lua_pushcclosure( L2, cfunc, n ); // eats up upvalues + } else { + // Set upvalues (originally set to 'nil' by 'lua_load') + // + int func_index= lua_gettop(L2)-n; + + for( ; n>0; n-- ) { + const char *rc= lua_setupvalue( L2, func_index, n ); + // + // "assigns the value at the top of the stack to the upvalue and returns its name. + // It also pops the value from the stack." + + ASSERT_L(rc); // not having enough slots? + } + } + STACK_END(L,0) +} + + +/* +* Copies a value from 'L' state (at index 'i') to 'L2' state. Does not remove +* the original value. +* +* NOTE: Both the states must be solely in the current OS thread's posession. +* +* 'i' is an absolute index (no -1, ...) +* +* Returns TRUE if value was pushed, FALSE if its type is non-supported. +*/ +static bool_t inter_copy_one_( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i, enum e_vt vt ) +{ + bool_t ret= TRUE; + + STACK_GROW( L2, 1 ); + + STACK_CHECK(L2) + + switch ( lua_type(L,i) ) { + /* Basic types allowed both as values, and as table keys */ + + case LUA_TBOOLEAN: + lua_pushboolean( L2, lua_toboolean(L, i) ); + break; + + case LUA_TNUMBER: + /* LNUM patch support (keeping integer accuracy) */ +#ifdef LUA_LNUM + if (lua_isinteger(L,i)) { + lua_pushinteger( L2, lua_tointeger(L, i) ); + break; + } +#endif + lua_pushnumber( L2, lua_tonumber(L, i) ); + break; + + case LUA_TSTRING: { + size_t len; const char *s = lua_tolstring( L, i, &len ); + lua_pushlstring( L2, s, len ); + } break; + + case LUA_TLIGHTUSERDATA: + lua_pushlightuserdata( L2, lua_touserdata(L, i) ); + break; + + /* The following types are not allowed as table keys */ + + case LUA_TUSERDATA: if (vt==VT_KEY) { ret=FALSE; break; } + /* Allow only deep userdata entities to be copied across + */ + if (!luaG_copydeep( L, L2, i )) { + // Cannot copy it full; copy as light userdata + // + lua_pushlightuserdata( L2, lua_touserdata(L, i) ); + } break; + + case LUA_TNIL: if (vt==VT_KEY) { ret=FALSE; break; } + lua_pushnil(L2); + break; + + case LUA_TFUNCTION: if (vt==VT_KEY) { ret=FALSE; break; } { + /* + * Passing C functions is risky; if they refer to LUA_ENVIRONINDEX + * and/or LUA_REGISTRYINDEX they might work unintended (not work) + * at the target. + * + * On the other hand, NOT copying them causes many self tests not + * to work (timer, hangtest, ...) + * + * The trouble is, we cannot KNOW if the function at hand is safe + * or not. We cannot study it's behaviour. We could trust the user, + * but they might not even know they're sending lua_CFunction over + * (as upvalues etc.). + */ +#if 0 + if (lua_iscfunction(L,i)) + luaL_error( L, "Copying lua_CFunction between Lua states is risky, and currently disabled." ); +#endif + STACK_CHECK(L2) + push_cached_func( L2, L2_cache_i, L, i ); + ASSERT_L( lua_isfunction(L2,-1) ); + STACK_END(L2,1) + } break; + + case LUA_TTABLE: if (vt==VT_KEY) { ret=FALSE; break; } { + + STACK_CHECK(L) + STACK_CHECK(L2) + + /* Check if we've already copied the same table from 'L' (during this transmission), and + * reuse the old copy. This allows table upvalues shared by multiple + * local functions to point to the same table, also in the target. + * Also, this takes care of cyclic tables and multiple references + * to the same subtable. + * + * Note: Even metatables need to go through this test; to detect + * loops s.a. those in required module tables (getmetatable(lanes).lanes == lanes) + */ + if (push_cached_table( L2, L2_cache_i, L, i )) { + ASSERT_L( lua_istable(L2, -1) ); // from cache + break; + } + ASSERT_L( lua_istable(L2,-1) ); + + STACK_GROW( L, 2 ); + STACK_GROW( L2, 2 ); + + lua_pushnil(L); // start iteration + while( lua_next( L, i ) ) { + uint_t val_i= lua_gettop(L); + uint_t key_i= val_i-1; + + /* Only basic key types are copied over; others ignored + */ + if (inter_copy_one_( L2, 0 /*key*/, L, key_i, VT_KEY )) { + /* + * Contents of metatables are copied with cache checking; + * important to detect loops. + */ + if (inter_copy_one_( L2, L2_cache_i, L, val_i, VT_NORMAL )) { + ASSERT_L( lua_istable(L2,-3) ); + lua_rawset( L2, -3 ); // add to table (pops key & val) + } else { + luaL_error( L, "Unable to copy over type '%s' (in %s)", + luaG_typename(L,val_i), + vt==VT_NORMAL ? "table":"metatable" ); + } + } + lua_pop( L, 1 ); // pop value (next round) + } + STACK_MID(L,0) + STACK_MID(L2,1) + + /* Metatables are expected to be immutable, and copied only once. + */ + if (lua_getmetatable( L, i )) { + // + // L [-1]: metatable + + uint_t mt_id= get_mt_id( L, -1 ); // Unique id for the metatable + + STACK_GROW(L2,4); + + push_registry_subtable( L2, REG_MTID ); + STACK_MID(L2,2); + lua_pushinteger( L2, mt_id ); + lua_rawget( L2, -2 ); + // + // L2 ([-3]: copied table) + // [-2]: reg[REG_MTID] + // [-1]: nil/metatable pre-known in L2 + + STACK_MID(L2,3); + + if (lua_isnil(L2,-1)) { /* L2 did not know the metatable */ + lua_pop(L2,1); + STACK_MID(L2,2); +ASSERT_L( lua_istable(L,-1) ); + if (inter_copy_one_( L2, L2_cache_i /*for function cacheing*/, L, lua_gettop(L) /*[-1]*/, VT_METATABLE )) { + // + // L2 ([-3]: copied table) + // [-2]: reg[REG_MTID] + // [-1]: metatable (copied from L) + + STACK_MID(L2,3); + // mt_id -> metatable + // + lua_pushinteger(L2,mt_id); + lua_pushvalue(L2,-2); + lua_rawset(L2,-4); + + // metatable -> mt_id + // + lua_pushvalue(L2,-1); + lua_pushinteger(L2,mt_id); + lua_rawset(L2,-4); + + STACK_MID(L2,3); + } else { + luaL_error( L, "Error copying a metatable" ); + } + STACK_MID(L2,3); + } + // L2 ([-3]: copied table) + // [-2]: reg[REG_MTID] + // [-1]: metatable (pre-known or copied from L) + + lua_remove(L2,-2); // take away 'reg[REG_MTID]' + // + // L2: ([-2]: copied table) + // [-1]: metatable for that table + + lua_setmetatable( L2, -2 ); + + // L2: [-1]: copied table (with metatable set if source had it) + + lua_pop(L,1); // remove source metatable (L, not L2!) + } + STACK_END(L2,1) + STACK_END(L,0) + } break; + + /* The following types cannot be copied */ + + case LUA_TTHREAD: + ret=FALSE; break; + } + + STACK_END(L2, ret? 1:0) + + return ret; +} + + +/* +* Akin to 'lua_xmove' but copies values between _any_ Lua states. +* +* NOTE: Both the states must be solely in the current OS thread's posession. +* +* Note: Parameters are in this order ('L' = from first) to be same as 'lua_xmove'. +*/ +void luaG_inter_copy( lua_State* L, lua_State *L2, uint_t n ) +{ + uint_t top_L= lua_gettop(L); + uint_t top_L2= lua_gettop(L2); + uint_t i; + + /* steal Lua library's 'luaB_tostring()' from the first call. Other calls + * don't have to have access to it. + * + * Note: multiple threads won't come here at once; this function will + * be called before there can be multiple threads (no locking needed). + */ + if (!hijacked_tostring) { + STACK_GROW( L,1 ); + + STACK_CHECK(L) + lua_getglobal( L, "tostring" ); + // + // [-1]: function|nil + + hijacked_tostring= lua_tocfunction( L, -1 ); + lua_pop(L,1); + STACK_END(L,0) + + if (!hijacked_tostring) { + luaL_error( L, "Need to see 'tostring()' once" ); + } + } + + if (n > top_L) + luaL_error( L, "Not enough values: %d < %d", top_L, n ); + + STACK_GROW( L2, n+1 ); + + /* + * Make a cache table for the duration of this copy. Collects tables and + * function entries, avoiding the same entries to be passed on as multiple + * copies. ESSENTIAL i.e. for handling upvalue tables in the right manner! + */ + lua_newtable(L2); + + for (i=top_L-n+1; i <= top_L; i++) { + if (!inter_copy_one_( L2, top_L2+1, L, i, VT_NORMAL )) { + + luaL_error( L, "Cannot copy type: %s", luaG_typename(L,i) ); + } + } + + /* + * Remove the cache table. Persistant caching would cause i.e. multiple + * messages passed in the same table to use the same table also in receiving + * end. + */ + lua_remove( L2, top_L2+1 ); + + ASSERT_L( (uint_t)lua_gettop(L) == top_L ); + ASSERT_L( (uint_t)lua_gettop(L2) == top_L2+n ); +} + + +void luaG_inter_move( lua_State* L, lua_State *L2, uint_t n ) +{ + luaG_inter_copy( L, L2, n ); + lua_pop( L,(int)n ); +} diff --git a/src/lualanes/tools.h b/src/lualanes/tools.h new file mode 100644 index 0000000000000000000000000000000000000000..d155c65eb65447ebccd76ef1a6bf49eee5b96f5a --- /dev/null +++ b/src/lualanes/tools.h @@ -0,0 +1,72 @@ +/* +* TOOLS.H +*/ +#ifndef TOOLS_H +#define TOOLS_H + +#include "lua.h" +#include "threading.h" + // MUTEX_T + +#include <assert.h> + +// Note: The < -10000 test is to leave registry/global/upvalue indices untouched +// +#define /*int*/ STACK_ABS(L,n) \ + ( ((n) >= 0 || (n) <= -10000) ? (n) : lua_gettop(L) +(n) +1 ) + +#ifdef NDEBUG + #define _ASSERT_L(lua,c) /*nothing*/ + #define STACK_CHECK(L) /*nothing*/ + #define STACK_MID(L,c) /*nothing*/ + #define STACK_END(L,c) /*nothing*/ + #define STACK_DUMP(L) /*nothing*/ + #define DEBUG() /*nothing*/ +#else + #define _ASSERT_L(lua,c) { if (!(c)) luaL_error( lua, "ASSERT failed: %s:%d '%s'", __FILE__, __LINE__, #c ); } + // + #define STACK_CHECK(L) { int _oldtop_##L = lua_gettop(L); + #define STACK_MID(L,change) { int a= lua_gettop(L)-_oldtop_##L; int b= (change); \ + if (a != b) luaL_error( L, "STACK ASSERT failed (%d not %d): %s:%d", a, b, __FILE__, __LINE__ ); } + #define STACK_END(L,change) STACK_MID(L,change) } + + #define STACK_DUMP(L) luaG_dump(L); + #define DEBUG() fprintf( stderr, "<<%s %d>>\n", __FILE__, __LINE__ ); +#endif +#define ASSERT_L(c) _ASSERT_L(L,c) + +#define STACK_GROW(L,n) { if (!lua_checkstack(L,n)) luaL_error( L, "Cannot grow stack!" ); } + +#define LUAG_FUNC( func_name ) static int LG_##func_name( lua_State *L ) + +#define luaG_optunsigned(L,i,d) ((uint_t) luaL_optinteger(L,i,d)) +#define luaG_tounsigned(L,i) ((uint_t) lua_tointeger(L,i)) + +#define luaG_isany(L,i) (!lua_isnil(L,i)) + +#define luaG_typename( L, index ) lua_typename( L, lua_type(L,index) ) + +void luaG_dump( lua_State* L ); + +const char *luaG_openlibs( lua_State *L, const char *libs ); + +int luaG_deep_userdata( lua_State *L ); +void *luaG_todeep( lua_State *L, lua_CFunction idfunc, int index ); + +typedef struct { + volatile int refcount; + void *deep; +} DEEP_PRELUDE; + +void luaG_push_proxy( lua_State *L, lua_CFunction idfunc, DEEP_PRELUDE *deep_userdata ); + +void luaG_inter_copy( lua_State *L, lua_State *L2, uint_t n ); +void luaG_inter_move( lua_State *L, lua_State *L2, uint_t n ); + +// Lock for reference counter inc/dec locks (to be initialized by outside code) +// +extern MUTEX_T deep_lock; +extern MUTEX_T mtid_lock; + +#endif + // TOOLS_H diff --git a/src/luasocket/tcp.c b/src/luasocket/tcp.c index 6b8a79b4bc555dad97aac8e6c82b01c170bbec47..80eb8b317a12921617eff4e1067c30dede596da9 100644 --- a/src/luasocket/tcp.c +++ b/src/luasocket/tcp.c @@ -30,6 +30,7 @@ static int meth_getpeername(lua_State *L); static int meth_shutdown(lua_State *L); static int meth_receive(lua_State *L); static int meth_accept(lua_State *L); +static int meth_acceptfd(lua_State *L); static int meth_close(lua_State *L); static int meth_setoption(lua_State *L); static int meth_settimeout(lua_State *L); @@ -42,6 +43,7 @@ static luaL_reg tcp[] = { {"__gc", meth_close}, {"__tostring", auxiliar_tostring}, {"accept", meth_accept}, + {"acceptfd", meth_acceptfd}, {"bind", meth_bind}, {"close", meth_close}, {"connect", meth_connect}, @@ -185,6 +187,27 @@ static int meth_accept(lua_State *L) } } +/*-------------------------------------------------------------------------*\ +* Waits for and returns a client object attempting connection to the +* server object +\*-------------------------------------------------------------------------*/ +static int meth_acceptfd(lua_State *L) +{ + p_tcp server = (p_tcp) auxiliar_checkclass(L, "tcp{server}", 1); + p_timeout tm = timeout_markstart(&server->tm); + t_socket sock; + int err = socket_accept(&server->sock, &sock, NULL, NULL, tm); + /* if successful, push client socket */ + if (err == IO_DONE) { + lua_pushnumber(L, sock); + return 1; + } else { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(err)); + return 2; + } +} + /*-------------------------------------------------------------------------*\ * Binds an object to an address \*-------------------------------------------------------------------------*/ @@ -316,12 +339,19 @@ static int meth_settimeout(lua_State *L) static int global_create(lua_State *L) { t_socket sock; - const char *err = inet_trycreate(&sock, SOCK_STREAM); + const char *err = NULL; + int fd = luaL_optnumber(L, 1, -1); + if (fd < 1) + err = inet_trycreate(&sock, SOCK_STREAM); + else + sock = fd; /* try to allocate a system socket */ if (!err) { /* allocate tcp object */ p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); - /* set its type as master object */ + if (fd >= 1) + auxiliar_setclass(L, "tcp{client}", -1); + else auxiliar_setclass(L, "tcp{master}", -1); /* initialize remaining structure fields */ socket_setnonblocking(&sock);