diff options
Diffstat (limited to 'src/lanes.lua')
-rw-r--r-- | src/lanes.lua | 323 |
1 files changed, 163 insertions, 160 deletions
diff --git a/src/lanes.lua b/src/lanes.lua index 1d50f97..f4eef81 100644 --- a/src/lanes.lua +++ b/src/lanes.lua | |||
@@ -123,9 +123,6 @@ lanes.configure = function( _params) | |||
123 | 123 | ||
124 | local set_singlethreaded = assert( core.set_singlethreaded) | 124 | local set_singlethreaded = assert( core.set_singlethreaded) |
125 | 125 | ||
126 | local now_secs = assert( core.now_secs) | ||
127 | local wakeup_conv = assert( core.wakeup_conv) | ||
128 | |||
129 | local max_prio = assert( core.max_prio) | 126 | local max_prio = assert( core.max_prio) |
130 | 127 | ||
131 | lanes.ABOUT= | 128 | lanes.ABOUT= |
@@ -311,6 +308,7 @@ local linda = core.linda | |||
311 | 308 | ||
312 | -- PUBLIC LANES API | 309 | -- PUBLIC LANES API |
313 | local timer = function() error "timers are not active" end | 310 | local timer = function() error "timers are not active" end |
311 | local timer_lane = nil | ||
314 | local timers = timer | 312 | local timers = timer |
315 | 313 | ||
316 | if _params.with_timers ~= false then | 314 | if _params.with_timers ~= false then |
@@ -338,158 +336,161 @@ timer_gateway:set(first_time_key,true) | |||
338 | -- has 'table' always declared) | 336 | -- has 'table' always declared) |
339 | -- | 337 | -- |
340 | if first_time then | 338 | if first_time then |
341 | local table_remove= assert( table.remove ) | ||
342 | local table_insert= assert( table.insert ) | ||
343 | 339 | ||
344 | -- | 340 | ----- |
345 | -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, | 341 | -- Snore loop (run as a lane on the background) |
346 | -- [key]= { wakeup_secs [,period_secs] } [, ...] }, | 342 | -- |
347 | -- } | 343 | -- High priority, to get trustworthy timings. |
348 | -- | 344 | -- |
349 | -- Collection of all running timers, indexed with linda's & key. | 345 | -- We let the timer lane be a "free running" thread; no handle to it |
350 | -- | 346 | -- remains. |
351 | -- Note that we need to use the deep lightuserdata identifiers, instead | 347 | -- |
352 | -- of 'linda_h' themselves as table indices. Otherwise, we'd get multiple | 348 | local timer_body = function() |
353 | -- entries for the same timer. | 349 | -- require lanes.core inside the timer body to prevent pulling now_secs() through an uvpvalue |
354 | -- | 350 | local core = require "lanes.core" |
355 | -- The 'hidden' reference to Linda proxy is used in 'check_timers()' but | 351 | |
356 | -- also important to keep the Linda alive, even if all outside world threw | 352 | -- |
357 | -- away pointers to it (which would ruin uniqueness of the deep pointer). | 353 | -- { [deep_linda_lightuserdata]= { [deep_linda_lightuserdata]=linda_h, |
358 | -- Now we're safe. | 354 | -- [key]= { wakeup_secs [,period_secs] } [, ...] }, |
359 | -- | 355 | -- } |
360 | local collection= {} | 356 | -- |
361 | 357 | -- Collection of all running timers, indexed with linda's & key. | |
362 | local function get_timers() | 358 | -- |
363 | local r = {} | 359 | -- Note that we need to use the deep lightuserdata identifiers, instead |
364 | for deep, t in pairs( collection) do | 360 | -- of 'linda_h' themselves as table indices. Otherwise, we'd get multiple |
365 | -- WR( tostring( deep)) | 361 | -- entries for the same timer. |
366 | local l = t[deep] | 362 | -- |
367 | for key, timer_data in pairs( t) do | 363 | -- The 'hidden' reference to Linda proxy is used in 'check_timers()' but |
368 | if key ~= deep then | 364 | -- also important to keep the Linda alive, even if all outside world threw |
369 | table_insert( r, {l, key, timer_data}) | 365 | -- away pointers to it (which would ruin uniqueness of the deep pointer). |
370 | end | 366 | -- Now we're safe. |
371 | end | 367 | -- |
372 | end | 368 | local collection = {} |
373 | return r | 369 | local table_insert = assert( table.insert) |
374 | end | 370 | |
375 | -- | 371 | local get_timers = function() |
376 | -- set_timer( linda_h, key [,wakeup_at_secs [,period_secs]] ) | 372 | local r = {} |
377 | -- | 373 | for deep, t in pairs( collection) do |
378 | local function set_timer( linda, key, wakeup_at, period ) | 374 | -- WR( tostring( deep)) |
379 | assert( wakeup_at==nil or wakeup_at>0.0 ) | 375 | local l = t[deep] |
380 | assert( period==nil or period>0.0 ) | 376 | for key, timer_data in pairs( t) do |
377 | if key ~= deep then | ||
378 | table_insert( r, {l, key, timer_data}) | ||
379 | end | ||
380 | end | ||
381 | end | ||
382 | return r | ||
383 | end -- get_timers() | ||
381 | 384 | ||
382 | local linda_deep= linda:deep() | 385 | -- |
383 | assert( linda_deep ) | 386 | -- set_timer( linda_h, key [,wakeup_at_secs [,period_secs]] ) |
387 | -- | ||
388 | local set_timer = function( linda, key, wakeup_at, period) | ||
389 | assert( wakeup_at == nil or wakeup_at > 0.0) | ||
390 | assert( period == nil or period > 0.0) | ||
384 | 391 | ||
385 | -- Find or make a lookup for this timer | 392 | local linda_deep = linda:deep() |
386 | -- | 393 | assert( linda_deep) |
387 | local t1= collection[linda_deep] | 394 | |
388 | if not t1 then | 395 | -- Find or make a lookup for this timer |
389 | t1= { [linda_deep]= linda } -- proxy to use the Linda | 396 | -- |
390 | collection[linda_deep]= t1 | 397 | local t1 = collection[linda_deep] |
391 | end | 398 | if not t1 then |
392 | 399 | t1 = { [linda_deep] = linda} -- proxy to use the Linda | |
393 | if wakeup_at==nil then | 400 | collection[linda_deep] = t1 |
394 | -- Clear the timer | 401 | end |
395 | -- | 402 | |
396 | t1[key]= nil | 403 | if wakeup_at == nil then |
397 | 404 | -- Clear the timer | |
398 | -- Remove empty tables from collection; speeds timer checks and | 405 | -- |
399 | -- lets our 'safety reference' proxy be gc:ed as well. | 406 | t1[key]= nil |
400 | -- | 407 | |
401 | local empty= true | 408 | -- Remove empty tables from collection; speeds timer checks and |
402 | for k,_ in pairs(t1) do | 409 | -- lets our 'safety reference' proxy be gc:ed as well. |
403 | if k~= linda_deep then | 410 | -- |
404 | empty= false; break | 411 | local empty = true |
405 | end | 412 | for k, _ in pairs( t1) do |
406 | end | 413 | if k ~= linda_deep then |
407 | if empty then | 414 | empty = false |
408 | collection[linda_deep]= nil | 415 | break |
409 | end | 416 | end |
410 | 417 | end | |
411 | -- Note: any unread timer value is left at 'linda[key]' intensionally; | 418 | if empty then |
412 | -- clearing a timer just stops it. | 419 | collection[linda_deep] = nil |
413 | else | 420 | end |
414 | -- New timer or changing the timings | 421 | |
415 | -- | 422 | -- Note: any unread timer value is left at 'linda[key]' intensionally; |
416 | local t2= t1[key] | 423 | -- clearing a timer just stops it. |
417 | if not t2 then | 424 | else |
418 | t2= {}; t1[key]= t2 | 425 | -- New timer or changing the timings |
419 | end | 426 | -- |
420 | 427 | local t2 = t1[key] | |
421 | t2[1]= wakeup_at | 428 | if not t2 then |
422 | t2[2]= period -- can be 'nil' | 429 | t2= {} |
423 | end | 430 | t1[key]= t2 |
424 | end | 431 | end |
432 | |||
433 | t2[1] = wakeup_at | ||
434 | t2[2] = period -- can be 'nil' | ||
435 | end | ||
436 | end -- set_timer() | ||
437 | |||
438 | local now_secs = core.now_secs | ||
439 | assert( type( now_secs) == "function") | ||
440 | ----- | ||
441 | -- [next_wakeup_at]= check_timers() | ||
442 | -- Check timers, and wake up the ones expired (if any) | ||
443 | -- Returns the closest upcoming (remaining) wakeup time (or 'nil' if none). | ||
444 | local check_timers = function() | ||
445 | local now = now_secs() | ||
446 | local next_wakeup | ||
447 | |||
448 | for linda_deep,t1 in pairs(collection) do | ||
449 | for key,t2 in pairs(t1) do | ||
450 | -- | ||
451 | if key==linda_deep then | ||
452 | -- no 'continue' in Lua :/ | ||
453 | else | ||
454 | -- 't2': { wakeup_at_secs [,period_secs] } | ||
455 | -- | ||
456 | local wakeup_at= t2[1] | ||
457 | local period= t2[2] -- may be 'nil' | ||
458 | |||
459 | if wakeup_at <= now then | ||
460 | local linda= t1[linda_deep] | ||
461 | assert(linda) | ||
462 | |||
463 | linda:set( key, now ) | ||
464 | |||
465 | -- 'pairs()' allows the values to be modified (and even | ||
466 | -- removed) as far as keys are not touched | ||
467 | |||
468 | if not period then | ||
469 | -- one-time timer; gone | ||
470 | -- | ||
471 | t1[key]= nil | ||
472 | wakeup_at= nil -- no 'continue' in Lua :/ | ||
473 | else | ||
474 | -- repeating timer; find next wakeup (may jump multiple repeats) | ||
475 | -- | ||
476 | repeat | ||
477 | wakeup_at= wakeup_at+period | ||
478 | until wakeup_at > now | ||
479 | |||
480 | t2[1]= wakeup_at | ||
481 | end | ||
482 | end | ||
483 | |||
484 | if wakeup_at and ((not next_wakeup) or (wakeup_at < next_wakeup)) then | ||
485 | next_wakeup= wakeup_at | ||
486 | end | ||
487 | end | ||
488 | end -- t2 loop | ||
489 | end -- t1 loop | ||
490 | |||
491 | return next_wakeup -- may be 'nil' | ||
492 | end -- check_timers() | ||
425 | 493 | ||
426 | ----- | ||
427 | -- [next_wakeup_at]= check_timers() | ||
428 | -- | ||
429 | -- Check timers, and wake up the ones expired (if any) | ||
430 | -- | ||
431 | -- Returns the closest upcoming (remaining) wakeup time (or 'nil' if none). | ||
432 | -- | ||
433 | local function check_timers() | ||
434 | |||
435 | local now= now_secs() | ||
436 | local next_wakeup | ||
437 | |||
438 | for linda_deep,t1 in pairs(collection) do | ||
439 | for key,t2 in pairs(t1) do | ||
440 | -- | ||
441 | if key==linda_deep then | ||
442 | -- no 'continue' in Lua :/ | ||
443 | else | ||
444 | -- 't2': { wakeup_at_secs [,period_secs] } | ||
445 | -- | ||
446 | local wakeup_at= t2[1] | ||
447 | local period= t2[2] -- may be 'nil' | ||
448 | |||
449 | if wakeup_at <= now then | ||
450 | local linda= t1[linda_deep] | ||
451 | assert(linda) | ||
452 | |||
453 | linda:set( key, now ) | ||
454 | |||
455 | -- 'pairs()' allows the values to be modified (and even | ||
456 | -- removed) as far as keys are not touched | ||
457 | |||
458 | if not period then | ||
459 | -- one-time timer; gone | ||
460 | -- | ||
461 | t1[key]= nil | ||
462 | wakeup_at= nil -- no 'continue' in Lua :/ | ||
463 | else | ||
464 | -- repeating timer; find next wakeup (may jump multiple repeats) | ||
465 | -- | ||
466 | repeat | ||
467 | wakeup_at= wakeup_at+period | ||
468 | until wakeup_at > now | ||
469 | |||
470 | t2[1]= wakeup_at | ||
471 | end | ||
472 | end | ||
473 | |||
474 | if wakeup_at and ((not next_wakeup) or (wakeup_at < next_wakeup)) then | ||
475 | next_wakeup= wakeup_at | ||
476 | end | ||
477 | end | ||
478 | end -- t2 loop | ||
479 | end -- t1 loop | ||
480 | |||
481 | return next_wakeup -- may be 'nil' | ||
482 | end | ||
483 | |||
484 | ----- | ||
485 | -- Snore loop (run as a lane on the background) | ||
486 | -- | ||
487 | -- High priority, to get trustworthy timings. | ||
488 | -- | ||
489 | -- We let the timer lane be a "free running" thread; no handle to it | ||
490 | -- remains. | ||
491 | -- | ||
492 | local timer_body = function() | ||
493 | local timer_gateway_batched = timer_gateway.batched | 494 | local timer_gateway_batched = timer_gateway.batched |
494 | set_debug_threadname( "LanesTimer") | 495 | set_debug_threadname( "LanesTimer") |
495 | set_finalizer( function( err, stk) | 496 | set_finalizer( function( err, stk) |
@@ -502,7 +503,7 @@ if first_time then | |||
502 | end | 503 | end |
503 | end) | 504 | end) |
504 | while true do | 505 | while true do |
505 | local next_wakeup= check_timers() | 506 | local next_wakeup = check_timers() |
506 | 507 | ||
507 | -- Sleep until next timer to wake up, or a set/clear command | 508 | -- Sleep until next timer to wake up, or a set/clear command |
508 | -- | 509 | -- |
@@ -528,9 +529,9 @@ if first_time then | |||
528 | -- WR( "timer lane: no linda, aborted?") | 529 | -- WR( "timer lane: no linda, aborted?") |
529 | end | 530 | end |
530 | end | 531 | end |
531 | end | 532 | end -- timer_body() |
532 | gen( "*", { package= {}, priority = max_prio}, timer_body)() -- "*" instead of "io,package" for LuaJIT compatibility... | 533 | timer_lane = gen( "*", { package= {}, priority = max_prio}, timer_body)() -- "*" instead of "io,package" for LuaJIT compatibility... |
533 | end | 534 | end -- first_time |
534 | 535 | ||
535 | ----- | 536 | ----- |
536 | -- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) | 537 | -- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) |
@@ -540,13 +541,13 @@ timer = function( linda, key, a, period ) | |||
540 | if getmetatable( linda) ~= "Linda" then | 541 | if getmetatable( linda) ~= "Linda" then |
541 | error "expecting a Linda" | 542 | error "expecting a Linda" |
542 | end | 543 | end |
543 | if a==0.0 then | 544 | if a == 0.0 then |
544 | -- Caller expects to get current time stamp in Linda, on return | 545 | -- Caller expects to get current time stamp in Linda, on return |
545 | -- (like the timer had expired instantly); it would be good to set this | 546 | -- (like the timer had expired instantly); it would be good to set this |
546 | -- as late as possible (to give most current time) but also we want it | 547 | -- as late as possible (to give most current time) but also we want it |
547 | -- to precede any possible timers that might start striking. | 548 | -- to precede any possible timers that might start striking. |
548 | -- | 549 | -- |
549 | linda:set( key, now_secs() ) | 550 | linda:set( key, core.now_secs()) |
550 | 551 | ||
551 | if not period or period==0.0 then | 552 | if not period or period==0.0 then |
552 | timer_gateway:send( TGW_KEY, linda, key, nil, nil ) -- clear the timer | 553 | timer_gateway:send( TGW_KEY, linda, key, nil, nil ) -- clear the timer |
@@ -555,8 +556,8 @@ timer = function( linda, key, a, period ) | |||
555 | a= period | 556 | a= period |
556 | end | 557 | end |
557 | 558 | ||
558 | local wakeup_at= type(a)=="table" and wakeup_conv(a) -- given point of time | 559 | local wakeup_at= type(a)=="table" and core.wakeup_conv(a) -- given point of time |
559 | or (a and now_secs()+a or nil) | 560 | or (a and core.now_secs()+a or nil) |
560 | -- queue to timer | 561 | -- queue to timer |
561 | -- | 562 | -- |
562 | timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) | 563 | timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) |
@@ -639,15 +640,17 @@ local function genatomic( linda, key, initial_val ) | |||
639 | end | 640 | end |
640 | 641 | ||
641 | -- activate full interface | 642 | -- activate full interface |
643 | lanes.require = core.require | ||
642 | lanes.gen = gen | 644 | lanes.gen = gen |
643 | lanes.linda = core.linda | 645 | lanes.linda = core.linda |
644 | lanes.cancel_error = core.cancel_error | 646 | lanes.cancel_error = core.cancel_error |
645 | lanes.nameof = core.nameof | 647 | lanes.nameof = core.nameof |
646 | lanes.threads = (_params.track_lanes and core.threads) and core.threads or function() error "lane tracking is not available" end | 648 | lanes.threads = (_params.track_lanes and core.threads) and core.threads or function() error "lane tracking is not available" end |
647 | lanes.timer = timer | 649 | lanes.timer = timer |
650 | lanes.timer_lane = timer_lane | ||
648 | lanes.timers = timers | 651 | lanes.timers = timers |
649 | lanes.genlock = genlock | 652 | lanes.genlock = genlock |
650 | lanes.now_secs = now_secs | 653 | lanes.now_secs = core.now_secs |
651 | lanes.genatomic = genatomic | 654 | lanes.genatomic = genatomic |
652 | -- from now on, calling configure does nothing but checking that we don't call it with parameters that changed compared to the first invocation | 655 | -- from now on, calling configure does nothing but checking that we don't call it with parameters that changed compared to the first invocation |
653 | lanes.configure = function( _params2) | 656 | lanes.configure = function( _params2) |