diff --git a/CHANGES b/CHANGES index 0e638ca5a772a48533fb381cfd4c545283c802d9..a051c2968f1164d391b55ac98e85823f9eb1786b 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,13 @@ o Image.Dims.exif_get and Image.exif_decode according to the EXIF orientation information) without needing to check if the image is JPEG first. +o Concurrent + + - Add delay() to postpone a future. + + - Rewrite timeout() to reduce the memory footprint of a future in the + common case. + Bug fixes --------- diff --git a/lib/modules/Concurrent.pmod b/lib/modules/Concurrent.pmod index e31b37e11cd4115698c53944c1abf966bb803d50..eb91a0c88421918f03593ce9abe65a204aded559 100644 --- a/lib/modules/Concurrent.pmod +++ b/lib/modules/Concurrent.pmod @@ -91,8 +91,6 @@ class Future protected Pike.Backend backend; - protected array timeout_call_out_handle; - //! Set the backend to use for calling any callbacks. //! //! @note @@ -155,18 +153,7 @@ class Future //! @[set_backend()], @[use_backend()] protected void call_callback(function cb, mixed ... args) { - if (timeout_call_out_handle) { - // Remove the timeout call_out, as it will not be relevant, - // but holds a reference to us. - (backend?backend->remove_call_out:remove_call_out) - (timeout_call_out_handle); - timeout_call_out_handle = UNDEFINED; - } - if (backend) { - backend->call_out(cb, 0, @args); - } else { - callout(cb, 0, @args); - } + (backend ? backend->call_out : callout)(cb, 0, @args); } //! Wait for fulfillment. @@ -661,26 +648,45 @@ class Future return then(0, onrejected, @extra); } - //! Return a @[Future] that will either be fulfilled with the fulfilled - //! result of this @[Future], or be failed after @[seconds] have expired. - this_program timeout(int|float seconds) + private this_program setup_call_out(int|float seconds, void|int tout) { + array call_out_handle; Promise p = promise_factory(); + void cancelcout(mixed value) + { + (backend ? backend->remove_call_out : remove_call_out)(call_out_handle); + p->try_success(0); + } /* NB: try_* variants as the original promise may get fulfilled * after the timeout has occurred. */ - on_failure(p->try_failure); - on_success(p->try_success); - if (timeout_call_out_handle) { - // Remove the previous timeout call_out. - (backend?backend->remove_call_out:remove_call_out) - (timeout_call_out_handle); - } - timeout_call_out_handle = (backend?backend->call_out:call_out) - (p->try_failure, seconds, ({ "Timeout.\n", backtrace() })); + on_failure(cancelcout); + call_out_handle = (backend ? backend->call_out : call_out) + (p[tout ? "try_failure" : "try_success"], seconds, + tout && ({ "Timeout.\n", backtrace() })); + if (tout) + on_success(cancelcout); return p->future(); } + //! Return a @[Future] that will either be fulfilled with the fulfilled + //! result of this @[Future], or be failed after @[seconds] have expired. + this_program timeout(int|float seconds) + { + return first_completed( + ({ this_program::this, setup_call_out(seconds, 1) }) + ); + } + + //! Return a @[Future] that will be fulfilled with the fulfilled + //! result of this @[Future], but not until at least @[seconds] have passed. + this_program delay(int|float seconds) + { + return results( + ({ this_program::this, setup_call_out(seconds) }) + )->map(`[], 0); + } + protected string _sprintf(int t) { return t=='O' && sprintf("%O(%s,%O)", this_program, diff --git a/lib/modules/testsuite.in b/lib/modules/testsuite.in index 891cafbcc9f2d058145077842eaa5c789c1452be..2b0e6de71c78d54df442a16c2be10893ae9a53e5 100644 --- a/lib/modules/testsuite.in +++ b/lib/modules/testsuite.in @@ -299,6 +299,109 @@ test_do([[ add_constant("p13"); ]]) exit_promise(-1, 13) test_do([[ Concurrent.use_backend(1); ]]); +dnl - Promise.timeout() - true +init_promise() +test_do([[ add_constant("p11", Concurrent.Promise()); ]]) +test_do([[ add_constant("p12", Concurrent.Promise()); ]]) +test_do([[ add_constant("p13", Concurrent.Promise()); ]]) +test_do([[ add_constant("p212", Concurrent.Promise()); ]]) +test_do([[ add_constant("future", + promise->depend(({p11->future(),p12->future()})) + ->depend(({}))->depend(p13->future()) + ->depend(({p13->future(), p11->future(), + p212->timeout(0.01) + ->recover(lambda(mixed p) { return 212;})}))); ]]) +test_do([[ promise->depend()->success(14); ]]) +test_do([[ p13->success(13); ]]) +test_do([[ promise->depend()->success(15); ]]) +init_future() +test_do([[ p11->success(11); ]]) +test_do([[ p12->success(12); ]]) +test_do([[ sleep(0.1); ]]) +test_do([[ add_constant("p11"); ]]) +test_do([[ add_constant("p12"); ]]) +test_do([[ add_constant("p13"); ]]) +test_do([[ add_constant("p212"); ]]) +exit_promise(1, ({11, 12, 13, 13, 11, 212, 14, 15})) + +dnl - Promise.timeout() - false +init_promise() +test_do([[ add_constant("p11", Concurrent.Promise()); ]]) +test_do([[ add_constant("p12", Concurrent.Promise()); ]]) +test_do([[ add_constant("p13", Concurrent.Promise()); ]]) +test_do([[ add_constant("p212", Concurrent.Promise()); ]]) +test_do([[ add_constant("future", + promise->depend(({p11->future(),p12->future()})) + ->depend(({}))->depend(p13->future()) + ->depend(({p13->future(), p11->future(), + p212->timeout(9) + ->recover(lambda(mixed p) { return 212;})}))); ]]) +test_do([[ promise->depend()->success(14); ]]) +test_do([[ p13->success(13); ]]) +test_do([[ promise->depend()->success(15); ]]) +init_future() +test_do([[ p11->success(11); ]]) +test_do([[ p12->success(12); ]]) +test_do([[ sleep(0.1); ]]) +test_do([[ p212->success(312); ]]) +test_do([[ add_constant("p11"); ]]) +test_do([[ add_constant("p12"); ]]) +test_do([[ add_constant("p13"); ]]) +test_do([[ add_constant("p212"); ]]) +exit_promise(1, ({11, 12, 13, 13, 11, 312, 14, 15})) + +dnl - Promise.delay() - false +init_promise() +test_do([[ add_constant("p11", Concurrent.Promise()); ]]) +test_do([[ add_constant("p12", Concurrent.Promise()); ]]) +test_do([[ add_constant("p13", Concurrent.Promise()); ]]) +test_do([[ add_constant("p212", Concurrent.Promise()); ]]) +test_do([[ add_constant("future", + promise->depend(({p11->future(),p12->future()})) + ->depend(({}))->depend(p13->future()) + ->depend(({p13->future(), p11->future(), + p212->delay(9) + ->recover(lambda(mixed p) { return 212;})}))); ]]) +test_do([[ promise->depend()->success(14); ]]) +test_do([[ p13->success(13); ]]) +test_do([[ promise->depend()->success(15); ]]) +init_future() +test_do([[ p11->success(11); ]]) +test_do([[ p12->success(12); ]]) +test_do([[ sleep(0.01); ]]) +test_do([[ p212->failure(12); ]]) +test_do([[ add_constant("p11"); ]]) +test_do([[ add_constant("p12"); ]]) +test_do([[ add_constant("p13"); ]]) +test_do([[ add_constant("p212"); ]]) +exit_promise(1, ({11, 12, 13, 13, 11, 212, 14, 15})) + +dnl - Promise.delay() - true +init_promise() +test_do([[ add_constant("p11", Concurrent.Promise()); ]]) +test_do([[ add_constant("p12", Concurrent.Promise()); ]]) +test_do([[ add_constant("p13", Concurrent.Promise()); ]]) +test_do([[ add_constant("p212", Concurrent.Promise()); ]]) +test_do([[ add_constant("future", + promise->depend(({p11->future(),p12->future()})) + ->depend(({}))->depend(p13->future()) + ->depend(({p13->future(), p11->future(), + p212->delay(0.1)}))); ]]) +test_do([[ promise->depend()->success(14); ]]) +test_do([[ p13->success(13); ]]) +test_do([[ promise->depend()->success(15); ]]) +init_future() +test_do([[ p11->success(11); ]]) +test_do([[ p12->success(12); ]]) +test_do([[ sleep(0.01); ]]) +test_do([[ p212->success(212); ]]) +test_do([[ add_constant("p11"); ]]) +test_do([[ add_constant("p12"); ]]) +test_do([[ add_constant("p13"); ]]) +test_do([[ add_constant("p212"); ]]) +test_do([[ sleep(0.1); ]]) +exit_promise(1, ({11, 12, 13, 13, 11, 212, 14, 15})) + dnl - Promise.fold() - true init_promise() test_do([[ add_constant("p11", Concurrent.Promise()); ]])