Say I'm holding a value, but I want to turn it into a promise that fulfills delayed by a predetermined amount of time, any suggestions as to where to put a utility function like that?
The first thing that comes to mind would be to create a new member function of Concurrent.Future, i.e. next to the timeout() already there, we could introduce a delay() which would then semantically be something like:
Return a Future that will be fulfilled with the result of this Future no sooner than after delay seconds.
It's basically a sleep for promises. Any comments?
Stephen R. van den Berg wrote:
The first thing that comes to mind would be to create a new member function of Concurrent.Future, i.e. next to the timeout() already there, we could introduce a delay() which would then semantically be something like:
On second thought, that would increase the basic memory footprint of a Future to include extra state information.
Maybe a custom class called Concurrent.Delay() might be more appropriate, it would take a Future as argument, and return a delayed Future.
I'm sorry, isn't this just
void future_success(Promise p, float|int delay, mixed value) { call_out(p->success, delay, value); }
? Why would a new class or new state information be needed?
(Ok, a real implementation should of course check if set_backend() has been called in the Promise as well, but that doesn't change the principle of it.)
Marcus Comstedt (ACROSS) (Hail Ilpalazzo!) @ Pike (-) developers forum wrote:
(Also, it should use try_success(), not success().)
Details aside, a proper implementation also should cancel the callback when the promise fails (so the extra state here is the callout-reference).
Hm, ok. But I guess you could reuse the callout-reference used for timeout then? Because there is no point in having both a delayed success and a delayed failure at the same time (whichever would happen further ahead in time is a no-op).
Marcus Comstedt (ACROSS) (Hail Ilpalazzo!) @ Pike (-) developers forum wrote:
Hm, ok. But I guess you could reuse the callout-reference used for timeout then? Because there is no point in having both a delayed success and a delayed failure at the same time (whichever would happen further ahead in time is a no-op).
Well, I tried to argue this point to myself earlier, and I lost (I think).
Imagine this: delay(20) and timeout(30) both set on the same promise.
If the real result arrives after 15 seconds, it will be delayed for another 5 and then turned into a success. If it arrives after 25 seconds, it will be turned into a success without further delay. If it would have arrived after 35 seconds, it will be cut short as a failure at 30 seconds.
So, sadly, both call_outs make sense. The only thing I could do is make the call_out refs into an array with two fixed members. If both are not present (the common case), the array could be null.
I don't follow at all. Setting the delay before the real result has arrived doesn't mean anything. After 20 seconds there is nothing you can do if the result is not available, so setting a 20 second delay without setting a value is completely pointless. What you should do is call delay(5, value) when the value is available. Then the timeout can be removed becase 15 > 5. If the value arrives after 35 seconds, calling delay(-15, value) will throw because the promise has already been fulfilled (broken?) by the timeout. (You could provide a try_delay which does not throw as well.)
Anyway, if, for whatever reason, you just want to withhold success for the first N seconds from creation of your Promise, you should be able to do it by calling ->depend() with another Future that completes after N seconds, right?
Marcus Comstedt (ACROSS) (Hail Ilpalazzo!) @ Pike (-) developers forum wrote:
able to do it by calling ->depend() with another Future that completes after N seconds, right?
Correct. Which does mean that I still have to construct that generic "delay-promise". I'd like to give it a home somewhere, since it is not completely trivial. So any suggestions on a name and a spot in the namespace?
Stephen R. van den Berg wrote:
Marcus Comstedt (ACROSS) (Hail Ilpalazzo!) @ Pike (-) developers forum wrote:
able to do it by calling ->depend() with another Future that completes after N seconds, right?
Correct. Which does mean that I still have to construct that generic "delay-promise". I'd like to give it a home somewhere, since it is not completely trivial. So any suggestions on a name and a spot in the namespace?
On second thought, that might not be needed, maybe I can make it an internal construct that is invoked on delay() and then interjects a depend() using that.
After a bit of testing, I found this solution using only existing stuff:
Concurrent.Future delay(Concurrent.Future f, int|float t) { return Concurrent.all(f, Concurrent.Promise()->timeout(t)->then(`+, `+)) ->transform(`[], 0, 0); }
Could easily be made into a method of Future (using promise_factory()) of course.
Sorry, that should have been ->map(`[], 0) instead of ->transform(`[], 0, 0), otherwise failures are not propagated correctly...
Marcus Comstedt (ACROSS) (Hail Ilpalazzo!) @ Pike (-) developers forum wrote:
Sorry, that should have been ->map(`[], 0) instead of ->transform(`[], 0, 0), otherwise failures are not propagated correctly...
Thanks for the heads up. Already figured that out earlier. I now have something like this:
private int allzero(mixed x) { return 0; }
//! 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) { Promise p = promise_factory()->timeout(seconds)->recover(allzero); on_failure(p->try_failure); return results(({this_program::this, p->future()}))->map(`[], 0); }
Which does not quite work yet. Still trying to wrap my head around it (with Promises that always is an issue since it requires a mindset shift).
//! 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) { Promise p = promise_factory(); on_failure(p->try_failure); return depend( ({ p->timeout(seconds)->recover(allzero) }) ); }
This is the simplest version I could come up with, part of the Promise class, not Future because I needed the depend() member. It properly short-circuits the call_out by propagating the failure case.
Anything I missed?
P.S. I'm left wondering if perhaps timeout_call_out_handle can be eliminated from the Future class by using a depend() and some separate (temporary) structure.
Does that work for you? My initial attempts were along those lines, but failed to actually wait before returning the result. And when I try your version, I get the result immediately when I ->success() the original promise...
My variant with Concurrent.all does work as intended though, and does not require a Promise in.
Marcus Comstedt (ACROSS) (Hail Ilpalazzo!) @ Pike (-) developers forum wrote:
Does that work for you? My initial attempts were along those lines, but failed to actually wait before returning the result. And when I try your version, I get the result immediately when I ->success() the original promise...
Hmmm, when I try it by hand it seems to work like you describe (but that is without a running backend).
My variant with Concurrent.all does work as intended though, and does not require a Promise in.
I'll do some more tests to see what happens when, and will see if I can devise a testsuite entry.
Stephen R. van den Berg wrote:
Marcus Comstedt (ACROSS) (Hail Ilpalazzo!) @ Pike (-) developers forum wrote:
My variant with Concurrent.all does work as intended though, and does not require a Promise in.
I'll do some more tests to see what happens when, and will see if I can devise a testsuite entry.
Ok, revised it, reworked it, optimised the timeout_call_out_handle out of the standard Future class footprint along the way, and included four testsuite tests to test both delay() and timeout().
The total implementation of both timeout() and delay() member functions now consists of just:
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); } 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); }
Marcus Comstedt (ACROSS) (Hail Ilpalazzo!) @ Pike (-) developers forum wrote:
I don't follow at all. Setting the delay before the real result has arrived doesn't mean anything.
It depends on the semantics of the delay() function. Apparently what you have in mind and what I had were different (which is not to say that either one is better).
After 20 seconds there is nothing you can do if the result is not available, so setting a 20 second delay without setting a value is completely pointless.
In my case, I set a delay *always* without a value, I just tell the promise to pretend it has not been fulfilled for the first x seconds. So, if, after 20 seconds, the result is not available yet, I simply do nothing and wait for the real result to come in, which I then pass along promptly.
What you should do is call delay(5, value) when the value is available. Then the timeout can be removed becase 15 > 5. If the value arrives after 35 seconds, calling delay(-15, value) will throw because the promise has already been fulfilled (broken?) by the timeout. (You could provide a
This sounds complicated because it forces me to note times and do my own arithmetic to figure out how much time has passed.
I'd prefer it if (just like timeout()) the seconds passed to the delay function start counting from now (i.e. the moment I create the promise).
So, if I run like:
db->promise_query("SELECT * FROM largetable") .delay(20).timeout(30) .on_success(Yay) .on_failure(Boo);
Then: - If the SQL query takes 5 seconds, I expect Yay() to be called at t=20s - If the SQL query takes 10 seconds, I expect Yay() to be called at t=20s - If the SQL query takes 15 seconds, I expect Yay() to be called at t=20s - If the SQL query takes 25 seconds, I expect Yay() to be called at t=25s - If the SQL query takes 35 seconds, I expect Boo() to be called at t=30s
This only has a single timeframe reference, and is easy to follow (for me at least). Comments?
pike-devel@lists.lysator.liu.se