diff options
author | Benoit Germain <bnt period germain arrobase gmail period com> | 2014-01-16 14:17:23 +0100 |
---|---|---|
committer | Benoit Germain <bnt period germain arrobase gmail period com> | 2014-01-16 14:17:23 +0100 |
commit | 1843a0add00186eee129b0b0a2ee605866acbb61 (patch) | |
tree | 5de8d13e1b8495836f898c17790adf571f05365c /src | |
parent | baaea3cebf9d787f76557b68f18a012dc6adbbbc (diff) | |
download | lanes-1843a0add00186eee129b0b0a2ee605866acbb61.tar.gz lanes-1843a0add00186eee129b0b0a2ee605866acbb61.tar.bz2 lanes-1843a0add00186eee129b0b0a2ee605866acbb61.zip |
Cancellation improvements and some fixes
* bumped version to 3.7.8
* lane:cancel() now accepts a boolean second argument when soft
cancelling (negative timeout) to wake the thread if necessary
* if a blocked linda send() or receive() call is interrupted by a
cancellation request, it returns CANCEL_ERROR so that this case can be
differentiated from a simple timeout
* fixed WIN32 THREAD_CREATE() wrong _beginthreadex() error detection
* fatal WIN32 threading errors retrieve and output the error description
string with FormatMessage()
* fixed missing lanes.set_singlethreaded
* fixed perftest.lua
* added test/cancel.lua
Diffstat (limited to 'src')
-rw-r--r-- | src/lanes.c | 214 | ||||
-rw-r--r-- | src/lanes.lua | 2 | ||||
-rw-r--r-- | src/threading.c | 14 |
3 files changed, 125 insertions, 105 deletions
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 | } |