diff options
-rw-r--r-- | CHANGES | 11 | ||||
-rw-r--r-- | docs/index.html | 25 | ||||
-rw-r--r-- | src/lanes.c | 214 | ||||
-rw-r--r-- | src/lanes.lua | 2 | ||||
-rw-r--r-- | src/threading.c | 14 | ||||
-rw-r--r-- | tests/cancel.lua | 93 | ||||
-rw-r--r-- | tests/linda_perf.lua | 8 | ||||
-rw-r--r-- | tests/perftest.lua | 17 |
8 files changed, 258 insertions, 126 deletions
@@ -1,5 +1,16 @@ | |||
1 | CHANGES: | 1 | CHANGES: |
2 | 2 | ||
3 | CHANGE 90: BGe 16-Jan-14 | ||
4 | * version 3.7.8 | ||
5 | * lane:cancel() now accepts a boolean second argument when soft cancelling (negative timeout) to wake the thread if necessary | ||
6 | * if a blocked linda send() or receive() call is interrupted by a cancellation request, | ||
7 | it returns CANCEL_ERROR so that this case can be differentiated from a simple timeout | ||
8 | * fixed WIN32 THREAD_CREATE() wrong _beginthreadex() error detection | ||
9 | * fatal WIN32 threading errors retrieve and output the error description string with FormatMessage() | ||
10 | * fixed missing lanes.set_singlethreaded | ||
11 | * fixed perftest.lua | ||
12 | * added test/cancel.lua | ||
13 | |||
3 | CHANGE 89: BGe 09-Jan-14 | 14 | CHANGE 89: BGe 09-Jan-14 |
4 | * version 3.7.7 | 15 | * version 3.7.7 |
5 | * fix crash when calling linda:count() on unknown keys | 16 | * fix crash when calling linda:count() on unknown keys |
diff --git a/docs/index.html b/docs/index.html index c62f64f..3b64cb4 100644 --- a/docs/index.html +++ b/docs/index.html | |||
@@ -70,7 +70,7 @@ | |||
70 | </p> | 70 | </p> |
71 | 71 | ||
72 | <p> | 72 | <p> |
73 | This document was revised on 09-Jan-14, and applies to version <tt>3.7.7</tt>. | 73 | This document was revised on 16-Jan-14, and applies to version <tt>3.7.8</tt>. |
74 | </p> | 74 | </p> |
75 | </font> | 75 | </font> |
76 | </center> | 76 | </center> |
@@ -880,13 +880,18 @@ | |||
880 | <h2 id="cancelling">Cancelling</h2> | 880 | <h2 id="cancelling">Cancelling</h2> |
881 | 881 | ||
882 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> | 882 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> |
883 | bool[,reason] = lane_h:cancel( [timeout_secs=0.0,] [force_kill_bool = false] [, forcekill_timeout=0.0]) | 883 | bool[,reason] = lane_h:cancel( [positive_timeout_secs=0.0] [, force_kill_bool = false] [, forcekill_timeout=0.0]) |
884 | bool[,reason] = lane_h:cancel( negative_timeout_secs [, wake_bool = false]) | ||
884 | </pre></td></tr></table> | 885 | </pre></td></tr></table> |
885 | 886 | ||
886 | <p> | 887 | <p> |
887 | <tt>cancel()</tt> sends a cancellation request to the lane.<br/> | 888 | <tt>cancel()</tt> sends a cancellation request to the lane.<br/> |
888 | If <tt>timeout_secs</tt> is negative (aka "soft cancel"), starting with version 3.6.3, will only cause <tt>cancel_test()</tt> to return <tt>true</tt>, so that the lane can cleanup manually (the actual value is irrelevant). You can't provide the additional arguments in that case.<br/> | 889 | First argument is a timeout, that defaults to 0 if not specified. (Starting with version 3.6.3) signification of the following arguments differ depending on whether the timeout is negative or not. |
889 | If <tt>timeout_secs</tt> is positive (aka "hard cancel"), waits for the request to be processed, or a timeout to occur.<br/> | 890 | <br/> |
891 | If <tt>timeout_secs</tt> is negative (aka "soft cancel"), cancellation will only cause <tt>cancel_test()</tt> to return <tt>true</tt>, so that the lane can cleanup manually (the actual value is irrelevant). | ||
892 | If <tt>wake_bool</tt> is <tt>true</tt>, the lane is also signalled so that execution returns from any pending linda operation. | ||
893 | <br/> | ||
894 | If <tt>timeout_secs</tt> is positive (aka "hard cancel"), waits for the request to be processed, or a timeout to occur. Linda operations detecting the cancellation request will raise a special cancellation error (meaning they won't return in that case). | ||
890 | If <tt>force_kill_bool</tt> is <tt>true</tt>, <tt>forcekill_timeout</tt> can be set to tell how long lanes will wait for the OS thread to terminate before raising an error. Windows threads always terminate immediately, but it might not always be the case with some pthread implementations. | 895 | If <tt>force_kill_bool</tt> is <tt>true</tt>, <tt>forcekill_timeout</tt> can be set to tell how long lanes will wait for the OS thread to terminate before raising an error. Windows threads always terminate immediately, but it might not always be the case with some pthread implementations. |
891 | Returns <tt>true</tt> if soft cancelling, or the lane was already done (in <tt>"done"</tt>, <tt>"error"</tt> or <tt>"cancelled"</tt> status), or the cancellation was fruitful within <tt>timeout_secs</tt> timeout period. | 896 | Returns <tt>true</tt> if soft cancelling, or the lane was already done (in <tt>"done"</tt>, <tt>"error"</tt> or <tt>"cancelled"</tt> status), or the cancellation was fruitful within <tt>timeout_secs</tt> timeout period. |
892 | </p> | 897 | </p> |
@@ -1010,11 +1015,11 @@ | |||
1010 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> | 1015 | <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"><tr><td><pre> |
1011 | h = lanes.linda( [opt_name]) | 1016 | h = lanes.linda( [opt_name]) |
1012 | 1017 | ||
1013 | bool = h:send( [timeout_secs,] key, ...) | 1018 | bool|cancel_error = h:send( [timeout_secs,] key, ...) |
1014 | 1019 | ||
1015 | [key, val] = h:receive( [timeout_secs,] key [, ...]) | 1020 | [key, val]|[cancel_error] = h:receive( [timeout_secs,] key [, ...]) |
1016 | 1021 | ||
1017 | [key, val [, ...]] = h:receive( timeout, h.batched, key, n_uint_min[, n_uint_max]) | 1022 | [key, val [, ...]]|[cancel_error] = h:receive( timeout, h.batched, key, n_uint_min[, n_uint_max]) |
1018 | 1023 | ||
1019 | [true] = h:limit( key, n_uint) | 1024 | [true] = h:limit( key, n_uint) |
1020 | </pre></td></tr></table> | 1025 | </pre></td></tr></table> |
@@ -1041,12 +1046,16 @@ | |||
1041 | 1046 | ||
1042 | <p> | 1047 | <p> |
1043 | <tt>send()</tt> returns <tt>true</tt> if the sending succeeded, and <tt>false</tt> if the queue limit was met, and the queue did not empty enough during the given timeout. | 1048 | <tt>send()</tt> returns <tt>true</tt> if the sending succeeded, and <tt>false</tt> if the queue limit was met, and the queue did not empty enough during the given timeout. |
1049 | <br/> | ||
1050 | (Since version 3.7.8) <tt>send()</tt> returns <tt>lanes.cancel_error</tt> if interrupted by a soft cancel request. | ||
1044 | </p> | 1051 | </p> |
1045 | 1052 | ||
1046 | <p> | 1053 | <p> |
1047 | Equally, <tt>receive()</tt> returns a key and the value extracted from it, or nothing for timeout. Note that <tt>nil</tt>s can be sent and received; the <tt>key</tt> value will tell it apart from a timeout. | 1054 | Equally, <tt>receive()</tt> returns a key and the value extracted from it, or nothing for timeout. Note that <tt>nil</tt>s can be sent and received; the <tt>key</tt> value will tell it apart from a timeout. |
1048 | <br> | 1055 | <br/> |
1049 | Version 3.4.0 introduces an API change in the returned values: <tt>receive()</tt> returns the key followed by the value(s), in that order, and not the other way around. | 1056 | Version 3.4.0 introduces an API change in the returned values: <tt>receive()</tt> returns the key followed by the value(s), in that order, and not the other way around. |
1057 | <br/> | ||
1058 | (Since version 3.7.8) <tt>receive()</tt> returns <tt>lanes.cancel_error</tt> if interrupted by a soft cancel request. | ||
1050 | </p> | 1059 | </p> |
1051 | 1060 | ||
1052 | <p> | 1061 | <p> |
diff --git a/src/lanes.c b/src/lanes.c index ef44d90..cad8fb1 100644 --- a/src/lanes.c +++ b/src/lanes.c | |||
@@ -52,7 +52,7 @@ | |||
52 | * ... | 52 | * ... |
53 | */ | 53 | */ |
54 | 54 | ||
55 | char const* VERSION = "3.7.7"; | 55 | char const* VERSION = "3.7.8"; |
56 | 56 | ||
57 | /* | 57 | /* |
58 | =============================================================================== | 58 | =============================================================================== |
@@ -189,11 +189,59 @@ struct s_lane | |||
189 | // For tracking only | 189 | // For tracking only |
190 | }; | 190 | }; |
191 | 191 | ||
192 | static enum e_cancel_request cancel_test( lua_State* L); | 192 | // To allow free-running threads (longer lifespan than the handle's) |
193 | static void cancel_error( lua_State*L ); | 193 | // 'struct s_lane' are malloc/free'd and the handle only carries a pointer. |
194 | // This is not deep userdata since the handle's not portable among lanes. | ||
195 | // | ||
196 | #define lua_toLane( L, i) (*((struct s_lane**) lua_touserdata( L, i))) | ||
197 | |||
198 | #define CANCEL_TEST_KEY ((void*)get_lane) // used as registry key | ||
199 | static inline struct s_lane* get_lane( lua_State* L) | ||
200 | { | ||
201 | struct s_lane* s; | ||
202 | STACK_GROW( L, 1); | ||
203 | STACK_CHECK( L); | ||
204 | lua_pushlightuserdata( L, CANCEL_TEST_KEY); | ||
205 | lua_rawget( L, LUA_REGISTRYINDEX); | ||
206 | s = lua_touserdata( L, -1); // lightuserdata (true 's_lane' pointer) / nil | ||
207 | lua_pop( L, 1); | ||
208 | STACK_END( L, 0); | ||
209 | return s; | ||
210 | } | ||
211 | |||
212 | /* | ||
213 | * Check if the thread in question ('L') has been signalled for cancel. | ||
214 | * | ||
215 | * Called by cancellation hooks and/or pending Linda operations (because then | ||
216 | * the check won't affect performance). | ||
217 | * | ||
218 | * Returns TRUE if any locks are to be exited, and 'cancel_error()' called, | ||
219 | * to make execution of the lane end. | ||
220 | */ | ||
221 | static inline enum e_cancel_request cancel_test( lua_State* L) | ||
222 | { | ||
223 | struct s_lane* const s = get_lane( L); | ||
224 | // 's' is NULL for the original main state (and no-one can cancel that) | ||
225 | return s ? s->cancel_request : CANCEL_NONE; | ||
226 | } | ||
194 | 227 | ||
195 | #define CANCEL_TEST_KEY ((void*)cancel_test) // used as registry key | ||
196 | #define CANCEL_ERROR ((void*)cancel_error) // 'cancel_error' sentinel | 228 | #define CANCEL_ERROR ((void*)cancel_error) // 'cancel_error' sentinel |
229 | static int cancel_error( lua_State* L) | ||
230 | { | ||
231 | STACK_GROW( L, 1); | ||
232 | lua_pushlightuserdata( L, CANCEL_ERROR); // special error value | ||
233 | return lua_error( L); // doesn't return | ||
234 | } | ||
235 | |||
236 | static void cancel_hook( lua_State* L, lua_Debug* ar) | ||
237 | { | ||
238 | (void)ar; | ||
239 | if( cancel_test( L) != CANCEL_NONE) | ||
240 | { | ||
241 | cancel_error( L); | ||
242 | } | ||
243 | } | ||
244 | |||
197 | 245 | ||
198 | #if ERROR_FULL_STACK | 246 | #if ERROR_FULL_STACK |
199 | static int lane_error( lua_State* L); | 247 | static int lane_error( lua_State* L); |
@@ -383,6 +431,7 @@ static void check_key_types( lua_State*L, int _start, int _end) | |||
383 | * | 431 | * |
384 | * Returns: 'true' if the value was queued | 432 | * Returns: 'true' if the value was queued |
385 | * 'false' for timeout (only happens when the queue size is limited) | 433 | * 'false' for timeout (only happens when the queue size is limited) |
434 | * nil, CANCEL_ERROR if cancelled | ||
386 | */ | 435 | */ |
387 | LUAG_FUNC( linda_send) | 436 | LUAG_FUNC( linda_send) |
388 | { | 437 | { |
@@ -417,7 +466,7 @@ LUAG_FUNC( linda_send) | |||
417 | // convert nils to some special non-nil sentinel in sent values | 466 | // convert nils to some special non-nil sentinel in sent values |
418 | keeper_toggle_nil_sentinels( L, key_i + 1, 1); | 467 | keeper_toggle_nil_sentinels( L, key_i + 1, 1); |
419 | 468 | ||
420 | STACK_GROW(L, 1); | 469 | STACK_GROW( L, 1); |
421 | { | 470 | { |
422 | struct s_Keeper* K = keeper_acquire( linda); | 471 | struct s_Keeper* K = keeper_acquire( linda); |
423 | lua_State* KL = K ? K->L : NULL; // need to do this for 'STACK_CHECK' | 472 | lua_State* KL = K ? K->L : NULL; // need to do this for 'STACK_CHECK' |
@@ -451,26 +500,17 @@ LUAG_FUNC( linda_send) | |||
451 | } | 500 | } |
452 | /* limit faced; push until timeout */ | 501 | /* limit faced; push until timeout */ |
453 | 502 | ||
454 | cancel = cancel_test( L); // testing here causes no delays | ||
455 | if( cancel != CANCEL_NONE) // if user wants to cancel, the call returns without sending anything | ||
456 | { | 503 | { |
457 | break; | ||
458 | } | ||
459 | |||
460 | // change status of lane to "waiting" | ||
461 | { | ||
462 | struct s_lane* s; | ||
463 | enum e_status prev_status = ERROR_ST; // prevent 'might be used uninitialized' warnings | 504 | enum e_status prev_status = ERROR_ST; // prevent 'might be used uninitialized' warnings |
464 | STACK_GROW( L, 1); | 505 | struct s_lane* const s = get_lane( L); |
465 | |||
466 | STACK_CHECK( L); | ||
467 | lua_pushlightuserdata( L, CANCEL_TEST_KEY); | ||
468 | lua_rawget( L, LUA_REGISTRYINDEX); | ||
469 | s = lua_touserdata( L, -1); // lightuserdata (true 's_lane' pointer) or nil if in the main Lua state | ||
470 | lua_pop( L, 1); | ||
471 | STACK_END( L, 0); | ||
472 | if( s != NULL) | 506 | if( s != NULL) |
473 | { | 507 | { |
508 | cancel = s->cancel_request; // testing here causes no delays | ||
509 | if( cancel != CANCEL_NONE) // if user wants to cancel, the call returns without sending anything | ||
510 | { | ||
511 | break; | ||
512 | } | ||
513 | // change status of lane to "waiting" | ||
474 | prev_status = s->status; // RUNNING, most likely | 514 | prev_status = s->status; // RUNNING, most likely |
475 | ASSERT_L( prev_status == RUNNING); // but check, just in case | 515 | ASSERT_L( prev_status == RUNNING); // but check, just in case |
476 | s->status = WAITING; | 516 | s->status = WAITING; |
@@ -484,6 +524,8 @@ LUAG_FUNC( linda_send) | |||
484 | { | 524 | { |
485 | s->waiting_on = NULL; | 525 | s->waiting_on = NULL; |
486 | s->status = prev_status; | 526 | s->status = prev_status; |
527 | // if woken by a cancel request, be sure to handle it properly | ||
528 | cancel = s->cancel_request; | ||
487 | } | 529 | } |
488 | break; | 530 | break; |
489 | } | 531 | } |
@@ -504,12 +546,21 @@ LUAG_FUNC( linda_send) | |||
504 | return luaL_error( L, "tried to copy unsupported types"); | 546 | return luaL_error( L, "tried to copy unsupported types"); |
505 | } | 547 | } |
506 | 548 | ||
507 | // raise an error interrupting execution only in case of hard cancel | 549 | switch( cancel) |
508 | if( cancel == CANCEL_HARD) | 550 | { |
509 | cancel_error( L); | 551 | case CANCEL_SOFT: |
552 | // if user wants to soft-cancel, the call returns CANCEL_ERROR | ||
553 | lua_pushlightuserdata( L, CANCEL_ERROR); | ||
554 | return 1; | ||
510 | 555 | ||
511 | lua_pushboolean( L, ret); | 556 | case CANCEL_HARD: |
512 | return 1; | 557 | // raise an error interrupting execution only in case of hard cancel |
558 | return cancel_error( L); // raises an error and doesn't return | ||
559 | |||
560 | default: | ||
561 | lua_pushboolean( L, ret); // true (success) or false (timeout) | ||
562 | return 1; | ||
563 | } | ||
513 | } | 564 | } |
514 | 565 | ||
515 | 566 | ||
@@ -611,26 +662,17 @@ LUAG_FUNC( linda_receive) | |||
611 | } | 662 | } |
612 | /* nothing received; wait until timeout */ | 663 | /* nothing received; wait until timeout */ |
613 | 664 | ||
614 | cancel = cancel_test( L); // testing here causes no delays | ||
615 | if( cancel != CANCEL_NONE) // if user wants to cancel, the call returns without providing anything | ||
616 | { | 665 | { |
617 | break; | ||
618 | } | ||
619 | |||
620 | // change status of lane to "waiting" | ||
621 | { | ||
622 | struct s_lane* s; | ||
623 | enum e_status prev_status = ERROR_ST; // prevent 'might be used uninitialized' warnings | 666 | enum e_status prev_status = ERROR_ST; // prevent 'might be used uninitialized' warnings |
624 | STACK_GROW( L, 1); | 667 | struct s_lane* const s = get_lane( L); |
625 | |||
626 | STACK_CHECK( L); | ||
627 | lua_pushlightuserdata( L, CANCEL_TEST_KEY); | ||
628 | lua_rawget( L, LUA_REGISTRYINDEX); | ||
629 | s = lua_touserdata( L, -1); // lightuserdata (true 's_lane' pointer) or nil if in the main Lua state | ||
630 | lua_pop( L, 1); | ||
631 | STACK_END( L, 0); | ||
632 | if( s != NULL) | 668 | if( s != NULL) |
633 | { | 669 | { |
670 | cancel = s->cancel_request; // testing here causes no delays | ||
671 | if( cancel != CANCEL_NONE) // if user wants to cancel, the call returns without providing anything | ||
672 | { | ||
673 | break; | ||
674 | } | ||
675 | // change status of lane to "waiting" | ||
634 | prev_status = s->status; // RUNNING, most likely | 676 | prev_status = s->status; // RUNNING, most likely |
635 | ASSERT_L( prev_status == RUNNING); // but check, just in case | 677 | ASSERT_L( prev_status == RUNNING); // but check, just in case |
636 | s->status = WAITING; | 678 | s->status = WAITING; |
@@ -644,6 +686,8 @@ LUAG_FUNC( linda_receive) | |||
644 | { | 686 | { |
645 | s->waiting_on = NULL; | 687 | s->waiting_on = NULL; |
646 | s->status = prev_status; | 688 | s->status = prev_status; |
689 | // if woken by a cancel request, be sure to handle it properly | ||
690 | cancel = s->cancel_request; | ||
647 | } | 691 | } |
648 | break; | 692 | break; |
649 | } | 693 | } |
@@ -663,11 +707,20 @@ LUAG_FUNC( linda_receive) | |||
663 | return luaL_error( L, "tried to copy unsupported types"); | 707 | return luaL_error( L, "tried to copy unsupported types"); |
664 | } | 708 | } |
665 | 709 | ||
666 | // raise an error interrupting execution only in case of hard cancel | 710 | switch( cancel) |
667 | if( cancel == CANCEL_HARD) | 711 | { |
668 | cancel_error( L); | 712 | case CANCEL_SOFT: |
713 | // if user wants to soft-cancel, the call returns CANCEL_ERROR | ||
714 | lua_pushlightuserdata( L, CANCEL_ERROR); | ||
715 | return 1; | ||
669 | 716 | ||
670 | return pushed; | 717 | case CANCEL_HARD: |
718 | // raise an error interrupting execution only in case of hard cancel | ||
719 | return cancel_error( L); // raises an error and doesn't return | ||
720 | |||
721 | default: | ||
722 | return pushed; | ||
723 | } | ||
671 | } | 724 | } |
672 | 725 | ||
673 | 726 | ||
@@ -1225,6 +1278,14 @@ static cancel_result thread_cancel( lua_State* L, struct s_lane* s, double secs, | |||
1225 | { | 1278 | { |
1226 | s->cancel_request = CANCEL_SOFT; // it's now signaled to stop | 1279 | s->cancel_request = CANCEL_SOFT; // it's now signaled to stop |
1227 | // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own | 1280 | // negative timeout: we don't want to truly abort the lane, we just want it to react to cancel_test() on its own |
1281 | if( force) // wake the thread so that execution returns from any pending linda operation if desired | ||
1282 | { | ||
1283 | SIGNAL_T *waiting_on = s->waiting_on; | ||
1284 | if( s->status == WAITING && waiting_on != NULL) | ||
1285 | { | ||
1286 | SIGNAL_ALL( waiting_on); | ||
1287 | } | ||
1288 | } | ||
1228 | // say we succeeded though | 1289 | // say we succeeded though |
1229 | result = CR_Cancelled; | 1290 | result = CR_Cancelled; |
1230 | } | 1291 | } |
@@ -1509,54 +1570,6 @@ static int selfdestruct_gc( lua_State* L) | |||
1509 | } | 1570 | } |
1510 | 1571 | ||
1511 | 1572 | ||
1512 | // To allow free-running threads (longer lifespan than the handle's) | ||
1513 | // 'struct s_lane' are malloc/free'd and the handle only carries a pointer. | ||
1514 | // This is not deep userdata since the handle's not portable among lanes. | ||
1515 | // | ||
1516 | #define lua_toLane(L,i) (* ((struct s_lane**) lua_touserdata(L,i))) | ||
1517 | |||
1518 | |||
1519 | /* | ||
1520 | * Check if the thread in question ('L') has been signalled for cancel. | ||
1521 | * | ||
1522 | * Called by cancellation hooks and/or pending Linda operations (because then | ||
1523 | * the check won't affect performance). | ||
1524 | * | ||
1525 | * Returns TRUE if any locks are to be exited, and 'cancel_error()' called, | ||
1526 | * to make execution of the lane end. | ||
1527 | */ | ||
1528 | static enum e_cancel_request cancel_test( lua_State* L) | ||
1529 | { | ||
1530 | struct s_lane* s; | ||
1531 | |||
1532 | STACK_GROW( L, 1); | ||
1533 | |||
1534 | STACK_CHECK( L); | ||
1535 | lua_pushlightuserdata( L, CANCEL_TEST_KEY); | ||
1536 | lua_rawget( L, LUA_REGISTRYINDEX); | ||
1537 | s = lua_touserdata( L, -1); // lightuserdata (true 's_lane' pointer) / nil | ||
1538 | lua_pop( L, 1); | ||
1539 | STACK_END( L, 0); | ||
1540 | |||
1541 | // 's' is NULL for the original main state (no-one can cancel that) | ||
1542 | // | ||
1543 | return s ? s->cancel_request : CANCEL_NONE; | ||
1544 | } | ||
1545 | |||
1546 | static void cancel_error( lua_State*L ) { | ||
1547 | STACK_GROW(L,1); | ||
1548 | lua_pushlightuserdata( L, CANCEL_ERROR ); // special error value | ||
1549 | lua_error(L); // no return | ||
1550 | } | ||
1551 | |||
1552 | static void cancel_hook( lua_State*L, lua_Debug *ar ) | ||
1553 | { | ||
1554 | (void)ar; | ||
1555 | if( cancel_test( L) != CANCEL_NONE) | ||
1556 | cancel_error( L); | ||
1557 | } | ||
1558 | |||
1559 | |||
1560 | //--- | 1573 | //--- |
1561 | // bool = cancel_test() | 1574 | // bool = cancel_test() |
1562 | // | 1575 | // |
@@ -2294,7 +2307,7 @@ LUAG_FUNC( thread_gc) | |||
2294 | return 0; | 2307 | return 0; |
2295 | } | 2308 | } |
2296 | 2309 | ||
2297 | // lane_h:cancel( [timeout,] force[, forcekill_timeout]) | 2310 | // lane_h:cancel( [timeout] [, force [, forcekill_timeout]]) |
2298 | LUAG_FUNC( thread_cancel) | 2311 | LUAG_FUNC( thread_cancel) |
2299 | { | 2312 | { |
2300 | if( lua_gettop( L) < 1 || lua_type( L, 1) != LUA_TUSERDATA) | 2313 | if( lua_gettop( L) < 1 || lua_type( L, 1) != LUA_TUSERDATA) |
@@ -2311,10 +2324,11 @@ LUAG_FUNC( thread_cancel) | |||
2311 | if( lua_isnumber( L, 2)) | 2324 | if( lua_isnumber( L, 2)) |
2312 | { | 2325 | { |
2313 | secs = lua_tonumber( L, 2); | 2326 | secs = lua_tonumber( L, 2); |
2314 | if( secs < 0.0 && lua_gettop( L) > 2) | 2327 | if( secs < 0.0 && lua_gettop( L) > 3) |
2315 | { | 2328 | { |
2316 | return luaL_error( L, "can't force a soft cancel"); | 2329 | return luaL_error( L, "can't force_kill a soft cancel"); |
2317 | } | 2330 | } |
2331 | // negative timeout and force flag means we want to wake linda-waiting threads | ||
2318 | ++ force_i; | 2332 | ++ force_i; |
2319 | ++ forcekill_timeout_i; | 2333 | ++ forcekill_timeout_i; |
2320 | } | 2334 | } |
@@ -2330,16 +2344,16 @@ LUAG_FUNC( thread_cancel) | |||
2330 | 2344 | ||
2331 | switch( thread_cancel( L, s, secs, force, forcekill_timeout)) | 2345 | switch( thread_cancel( L, s, secs, force, forcekill_timeout)) |
2332 | { | 2346 | { |
2333 | case CR_Timeout: | 2347 | case CR_Timeout: |
2334 | lua_pushboolean( L, 0); | 2348 | lua_pushboolean( L, 0); |
2335 | lua_pushstring( L, "timeout"); | 2349 | lua_pushstring( L, "timeout"); |
2336 | return 2; | 2350 | return 2; |
2337 | 2351 | ||
2338 | case CR_Cancelled: | 2352 | case CR_Cancelled: |
2339 | lua_pushboolean( L, 1); | 2353 | lua_pushboolean( L, 1); |
2340 | return 1; | 2354 | return 1; |
2341 | 2355 | ||
2342 | case CR_Killed: | 2356 | case CR_Killed: |
2343 | lua_pushboolean( L, 0); | 2357 | lua_pushboolean( L, 0); |
2344 | lua_pushstring( L, "killed"); | 2358 | lua_pushstring( L, "killed"); |
2345 | return 2; | 2359 | return 2; |
diff --git a/src/lanes.lua b/src/lanes.lua index 0c544bb..e7b9715 100644 --- a/src/lanes.lua +++ b/src/lanes.lua | |||
@@ -128,7 +128,6 @@ lanes.configure = function( settings_) | |||
128 | end | 128 | end |
129 | local settings = core.configure and core.configure( params_checker( settings_)) or core.settings | 129 | local settings = core.configure and core.configure( params_checker( settings_)) or core.settings |
130 | local thread_new = assert( core.thread_new) | 130 | local thread_new = assert( core.thread_new) |
131 | local set_singlethreaded = assert( core.set_singlethreaded) | ||
132 | local max_prio = assert( core.max_prio) | 131 | local max_prio = assert( core.max_prio) |
133 | 132 | ||
134 | lanes.ABOUT = | 133 | lanes.ABOUT = |
@@ -664,6 +663,7 @@ end | |||
664 | lanes.linda = core.linda | 663 | lanes.linda = core.linda |
665 | lanes.cancel_error = core.cancel_error | 664 | lanes.cancel_error = core.cancel_error |
666 | lanes.nameof = core.nameof | 665 | lanes.nameof = core.nameof |
666 | lanes.set_singlethreaded = core.set_singlethreaded | ||
667 | lanes.threads = core.threads or function() error "lane tracking is not available" end -- core.threads isn't registered if settings.track_lanes is false | 667 | lanes.threads = core.threads or function() error "lane tracking is not available" end -- core.threads isn't registered if settings.track_lanes is false |
668 | lanes.set_thread_priority = core.set_thread_priority | 668 | lanes.set_thread_priority = core.set_thread_priority |
669 | lanes.timer = timer | 669 | lanes.timer = timer |
diff --git a/src/threading.c b/src/threading.c index 3014136..5a3e64b 100644 --- a/src/threading.c +++ b/src/threading.c | |||
@@ -87,11 +87,13 @@ THE SOFTWARE. | |||
87 | #if defined( PLATFORM_XBOX) || defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC) | 87 | #if defined( PLATFORM_XBOX) || defined( PLATFORM_WIN32) || defined( PLATFORM_POCKETPC) |
88 | static void FAIL( char const* funcname, int rc) | 88 | static void FAIL( char const* funcname, int rc) |
89 | { | 89 | { |
90 | fprintf( stderr, "%s() failed! (%d)\n", funcname, rc ); | 90 | char buf[256]; |
91 | FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM, NULL, rc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, 256, NULL); | ||
92 | fprintf( stderr, "%s() failed! [GetLastError() -> %d] '%s'", funcname, rc, buf); | ||
91 | #ifdef _MSC_VER | 93 | #ifdef _MSC_VER |
92 | __debugbreak(); // give a chance to the debugger! | 94 | __debugbreak(); // give a chance to the debugger! |
93 | #endif // _MSC_VER | 95 | #endif // _MSC_VER |
94 | abort(); | 96 | abort(); |
95 | } | 97 | } |
96 | #endif // win32 build | 98 | #endif // win32 build |
97 | 99 | ||
@@ -296,11 +298,15 @@ void THREAD_CREATE( THREAD_T* ref, THREAD_RETURN_T (__stdcall *func)( void*), vo | |||
296 | NULL // thread id (not used) | 298 | NULL // thread id (not used) |
297 | ); | 299 | ); |
298 | 300 | ||
299 | if( h == INVALID_HANDLE_VALUE) | 301 | if( h == NULL) // _beginthreadex returns 0L on failure instead of -1L (like _beginthread) |
302 | { | ||
300 | FAIL( "CreateThread", GetLastError()); | 303 | FAIL( "CreateThread", GetLastError()); |
304 | } | ||
301 | 305 | ||
302 | if (!SetThreadPriority( h, gs_prio_remap[prio + 3])) | 306 | if (!SetThreadPriority( h, gs_prio_remap[prio + 3])) |
307 | { | ||
303 | FAIL( "SetThreadPriority", GetLastError()); | 308 | FAIL( "SetThreadPriority", GetLastError()); |
309 | } | ||
304 | 310 | ||
305 | *ref = h; | 311 | *ref = h; |
306 | } | 312 | } |
diff --git a/tests/cancel.lua b/tests/cancel.lua new file mode 100644 index 0000000..0aab341 --- /dev/null +++ b/tests/cancel.lua | |||
@@ -0,0 +1,93 @@ | |||
1 | local lanes = require "lanes" .configure{ with_timers = false} | ||
2 | |||
3 | local linda = lanes.linda() | ||
4 | |||
5 | local laneBody = function( timeout_) | ||
6 | set_finalizer( function( err, stk) | ||
7 | if err == lanes.cancel_error then | ||
8 | -- note that we don't get the cancel_error when running wrapped inside a protected call if it doesn't rethrow it | ||
9 | print( " laneBody after cancel" ) | ||
10 | elseif err then | ||
11 | print( " laneBody error: "..tostring(err)) | ||
12 | else | ||
13 | print(" laneBody finalized") | ||
14 | end | ||
15 | end) | ||
16 | |||
17 | print( " entering lane with " .. tostring( timeout_) .. " timeout") | ||
18 | repeat | ||
19 | -- block-wait to be hard-cancelled | ||
20 | print " lane calling receive()" | ||
21 | local key, val = linda:receive( timeout_, "boob") | ||
22 | print( " receive() -> ", lanes.cancel_error == key and "cancel_error" or tostring( key), tostring( val)) | ||
23 | until cancel_test() -- soft cancel self test | ||
24 | print " shutting down after breaking out of loop" | ||
25 | end | ||
26 | |||
27 | local protectedBody = function( ...) | ||
28 | local ce = lanes.cancel_error | ||
29 | local errorHandler = function( _msg) | ||
30 | -- forward the message to the main thread that will display it with a popup | ||
31 | print( " error handler got ", ce == _msg and "cancel_error"or tostring( _msg)) | ||
32 | return _msg | ||
33 | end | ||
34 | -- Lua 5.1 doesn't pass additional xpcall arguments to the called function | ||
35 | -- therefore we need to create a closure that has no arguments but pulls everything from its upvalue | ||
36 | local params = {...} | ||
37 | local paramLessClosure = function() laneBody(table.unpack( params)) end | ||
38 | local status, message = xpcall( paramLessClosure, errorHandler) | ||
39 | if status == false then | ||
40 | print( " error handler rethrowing '" .. (ce == message and "cancel_error"or tostring( message)) .. "'") | ||
41 | -- if the error isn't rethrown, the lane's finalizer won't get it | ||
42 | error( message) | ||
43 | end | ||
44 | end | ||
45 | |||
46 | --#################################################################### | ||
47 | |||
48 | print "####################################################################\nbegin soft cancel test\n" | ||
49 | h = lanes.gen("*", protectedBody)( 0.666) | ||
50 | print "wait 3s" | ||
51 | linda:receive( 3, "yeah") | ||
52 | |||
53 | -- soft cancel | ||
54 | print "soft cancel with awakening" | ||
55 | h:cancel( -1, true) | ||
56 | |||
57 | -- wait 10s: the lane will interrupt its loop and print the exit message | ||
58 | print "wait 2s" | ||
59 | linda:receive( 2, "yeah") | ||
60 | |||
61 | --#################################################################### | ||
62 | |||
63 | print "\n\n####################################################################\nbegin hard cancel test\n" | ||
64 | h = lanes.gen("*", protectedBody)() | ||
65 | |||
66 | -- wait 3s before cancelling the lane | ||
67 | print "wait 3s" | ||
68 | linda:receive( 3, "yeah") | ||
69 | |||
70 | -- hard cancel and wait 10s: the lane will be interrupted from inside its current linda:receive() and won't return from it | ||
71 | print "hard cancel (always awakens)" | ||
72 | h:cancel() | ||
73 | |||
74 | print "wait 5s" | ||
75 | linda:receive( 5, "yeah") | ||
76 | |||
77 | --#################################################################### | ||
78 | |||
79 | print "\n\n####################################################################\nbegin hard cancel test with unprotected lane body\n" | ||
80 | h = lanes.gen("*", laneBody)() | ||
81 | |||
82 | -- wait 3s before cancelling the lane | ||
83 | print "wait 3s" | ||
84 | linda:receive( 3, "yeah") | ||
85 | |||
86 | -- hard cancel and wait 10s: the lane will be interrupted from inside its current linda:receive() and won't return from it | ||
87 | print "hard cancel (always awakens)" | ||
88 | h:cancel() | ||
89 | |||
90 | print "wait 5s" | ||
91 | linda:receive( 5, "yeah") | ||
92 | |||
93 | print "\ndone" \ No newline at end of file | ||
diff --git a/tests/linda_perf.lua b/tests/linda_perf.lua index ebe9eac..9348f71 100644 --- a/tests/linda_perf.lua +++ b/tests/linda_perf.lua | |||
@@ -34,7 +34,7 @@ local function ziva( preloop, loop, batch) | |||
34 | -- prefill the linda a bit to increase fifo stress | 34 | -- prefill the linda a bit to increase fifo stress |
35 | local top = math.max( preloop, loop) | 35 | local top = math.max( preloop, loop) |
36 | local l, lane = lanes.linda() | 36 | local l, lane = lanes.linda() |
37 | local t1 = os.time() | 37 | local t1 = lanes.now_secs() |
38 | for i = 1, preloop do | 38 | for i = 1, preloop do |
39 | l:send( "key", i) | 39 | l:send( "key", i) |
40 | end | 40 | end |
@@ -59,7 +59,7 @@ local function ziva( preloop, loop, batch) | |||
59 | end | 59 | end |
60 | l:send( "done" ,"are you happy?") | 60 | l:send( "done" ,"are you happy?") |
61 | lane:join() | 61 | lane:join() |
62 | return os.difftime(os.time(), t1) | 62 | return lanes.now_secs() - t1 |
63 | end | 63 | end |
64 | 64 | ||
65 | local tests = | 65 | local tests = |
@@ -149,7 +149,7 @@ local function ziva2( preloop, loop, batch) | |||
149 | l:receive( "key") | 149 | l:receive( "key") |
150 | end | 150 | end |
151 | end | 151 | end |
152 | local t1 = os.time() | 152 | local t1 = lanes.now_secs() |
153 | -- first, prime the linda with some data | 153 | -- first, prime the linda with some data |
154 | for i = 1, preloop, step do | 154 | for i = 1, preloop, step do |
155 | batch_send() | 155 | batch_send() |
@@ -165,7 +165,7 @@ local function ziva2( preloop, loop, batch) | |||
165 | for i = 1, preloop, step do | 165 | for i = 1, preloop, step do |
166 | batch_read() | 166 | batch_read() |
167 | end | 167 | end |
168 | return os.difftime(os.time(), t1) | 168 | return lanes.now_secs() - t1 |
169 | end | 169 | end |
170 | 170 | ||
171 | local tests2 = | 171 | local tests2 = |
diff --git a/tests/perftest.lua b/tests/perftest.lua index 4df2ad8..6ffc064 100644 --- a/tests/perftest.lua +++ b/tests/perftest.lua | |||
@@ -27,8 +27,7 @@ | |||
27 | local MSYS= os.getenv("OSTYPE")=="msys" | 27 | local MSYS= os.getenv("OSTYPE")=="msys" |
28 | 28 | ||
29 | 29 | ||
30 | local lanes = require "lanes" | 30 | local lanes = require "lanes".configure{ with_timers = false} |
31 | lanes.configure() | ||
32 | 31 | ||
33 | local m= require "argtable" | 32 | local m= require "argtable" |
34 | local argtable= assert( m.argtable ) | 33 | local argtable= assert( m.argtable ) |
@@ -36,7 +35,7 @@ local argtable= assert( m.argtable ) | |||
36 | local N= 1000 -- threads/loops to use | 35 | local N= 1000 -- threads/loops to use |
37 | local M= 1000 -- sieves from 1..M | 36 | local M= 1000 -- sieves from 1..M |
38 | local PLAIN= false -- single threaded (true) or using Lanes (false) | 37 | local PLAIN= false -- single threaded (true) or using Lanes (false) |
39 | local SINGLE= false -- cores to use (false / 1..n) | 38 | local SINGLE= 0 -- cores to use (0 / 1..n) |
40 | local TIME= false -- use Lua for the timing | 39 | local TIME= false -- use Lua for the timing |
41 | local PRIO_ODD, PRIO_EVEN -- -3..+3 | 40 | local PRIO_ODD, PRIO_EVEN -- -3..+3 |
42 | 41 | ||
@@ -63,7 +62,7 @@ end | |||
63 | for k,v in pairs( argtable(...) ) do | 62 | for k,v in pairs( argtable(...) ) do |
64 | if k==1 then N= tonumber(v) or HELP() | 63 | if k==1 then N= tonumber(v) or HELP() |
65 | elseif k=="plain" then PLAIN= true | 64 | elseif k=="plain" then PLAIN= true |
66 | elseif k=="single" then SINGLE= v -- true/number | 65 | elseif k=="single" then SINGLE= v -- number |
67 | elseif k=="time" then TIME= true | 66 | elseif k=="time" then TIME= true |
68 | elseif k=="prio" then PRIO_ODD, PRIO_EVEN= prio_param(v) | 67 | elseif k=="prio" then PRIO_ODD, PRIO_EVEN= prio_param(v) |
69 | else HELP() | 68 | else HELP() |
@@ -104,7 +103,7 @@ local function sieve_lane(N,id) | |||
104 | while 1 do | 103 | while 1 do |
105 | local n = g() | 104 | local n = g() |
106 | if n == nil then return end | 105 | if n == nil then return end |
107 | if math.mod(n, p) ~= 0 then coroutine.yield(n) end | 106 | if math.fmod(n, p) ~= 0 then coroutine.yield(n) end |
108 | end | 107 | end |
109 | end) | 108 | end) |
110 | end | 109 | end |
@@ -138,7 +137,7 @@ local f_odd= lanes.gen( "base,coroutine,math,table,io", -- "*" = all | |||
138 | 137 | ||
139 | io.stderr:write( "*** Counting primes 1.."..M.." "..N.." times ***\n\n" ) | 138 | io.stderr:write( "*** Counting primes 1.."..M.." "..N.." times ***\n\n" ) |
140 | 139 | ||
141 | local t0= TIME and os.time() | 140 | local t0= TIME and lanes.now_secs() |
142 | 141 | ||
143 | if PLAIN then | 142 | if PLAIN then |
144 | io.stderr:write( "Plain (no multithreading):\n" ) | 143 | io.stderr:write( "Plain (no multithreading):\n" ) |
@@ -148,9 +147,9 @@ if PLAIN then | |||
148 | assert( type(tmp)=="table" and tmp[1]==2 and tmp[168]==997 ) | 147 | assert( type(tmp)=="table" and tmp[1]==2 and tmp[168]==997 ) |
149 | end | 148 | end |
150 | else | 149 | else |
151 | if SINGLE then | 150 | if SINGLE > 0 then |
152 | io.stderr:write( (tonumber(SINGLE) and SINGLE or 1) .. " core(s):\n" ) | 151 | io.stderr:write( (tonumber(SINGLE) and SINGLE or 1) .. " core(s):\n" ) |
153 | lanes.single(SINGLE) -- limit to N cores (just OS X) | 152 | lanes.set_singlethreaded(SINGLE) -- limit to N cores (just OS X) |
154 | else | 153 | else |
155 | io.stderr:write( "Multi core:\n" ) | 154 | io.stderr:write( "Multi core:\n" ) |
156 | end | 155 | end |
@@ -177,7 +176,7 @@ end | |||
177 | io.stderr:write "\n" | 176 | io.stderr:write "\n" |
178 | 177 | ||
179 | if TIME then | 178 | if TIME then |
180 | local t= os.time() - t0 | 179 | local t= lanes.now_secs() - t0 |
181 | io.stderr:write( "*** TIMING: "..t.." seconds ***\n" ) | 180 | io.stderr:write( "*** TIMING: "..t.." seconds ***\n" ) |
182 | end | 181 | end |
183 | 182 | ||