FUTURES: await should complete the Future with Left(TimeoutException) if timeout happens
Investigate the implications
Also,
make sure that the onTimeout is called anyway.
Perhaps replace onTimeout with Future.awaitUsing(Scheduler)?
Also,
make sure that the onTimeout is called anyway.
Perhaps replace onTimeout with Future.awaitUsing(Scheduler)?
Leave a comment
on 2011-10-02 02:34 *
By Havoc Pennington
An issue is that if you await(customTime) where customTime is less than the future's regular expiration, I would think it's weird to complete the future with FutureTimeoutException. In other words the customTime should only affect that particular callsite of await(), not affect others interested in the future.
With the version of await that doesn't take a duration, then this doesn't come up, but then you have the problem that the two things called await work differently.
I made a patch to more generally always expire futures by completing them that may be interesting as a thought experiment:
https://github.com/havocp/akka/commits/timeouts-as-exceptions
(using a limited timer resolution to avoid a scheduler task for every single future).
In that patch, await() does not have limited resolution though, since it can set a precise time on the AtomicReference.
In that patch I made await() leave completing the future alone, so if await times out with a customTime that's shorter than the future's normal expiry, it throws but doesn't complete the future. I wonder if it would be nicer if await() had its own exception, like AwaitTimeoutException.
With the version of await that doesn't take a duration, then this doesn't come up, but then you have the problem that the two things called await work differently.
I made a patch to more generally always expire futures by completing them that may be interesting as a thought experiment:
https://github.com/havocp/akka/commits/timeouts-as-exceptions
(using a limited timer resolution to avoid a scheduler task for every single future).
In that patch, await() does not have limited resolution though, since it can set a precise time on the AtomicReference.
In that patch I made await() leave completing the future alone, so if await times out with a customTime that's shorter than the future's normal expiry, it throws but doesn't complete the future. I wonder if it would be nicer if await() had its own exception, like AwaitTimeoutException.
on 2011-10-06 02:15 *
By Havoc Pennington
Here's some more data and my argument from email, for whenever this bubbles to the top.
In addition to the Akka patch in the above link, I did some benchmarking here: https://github.com/havocp/expiration
Results from my laptop are:
The "item" that takes 11.259ms is queuing and expiring 100,000 objects immediately, so measuring pure book-keeping overhead. Doing the math, that is order of 100 nanoseconds time overhead per-future for either a custom batch-drain thing, or a simple concurrent queue solution as in the patch at https://github.com/havocp/akka/commits/timeouts-as-exceptions ; memory overhead for the concurrent queue is a linked list node, for the batch drain it's an array slot plus some other stuff amortized over the entire batch. Time overhead for a scheduleOnce per-future is order of 2000ns on my laptop, plus a fair memory overhead.
My thought is that the batch drain may not be worth the extra complexity but the concurrent queue solution is simple and pretty fast. Other solutions may be even better!
There is room for more micro-optimization, there's some debug stuff and virtualization that doesn't need to be there, and the function that rechecks the time remaining and reschedules batches could probably be redone to generate fewer intermediate objects and go faster. That function (clearBatch/recheck) seems to be the bottleneck on my laptop.
Pasting in my thoughts from the list, several things break if onComplete is not guaranteed to be called (if expiration is not a completion):
If you make expiration complete the future, there are still very good options for people who need maximum speed and/or precise timing:
(1% because say 50ms resolution on a 5s default timeout could get you a lot of batching for an app that's generating tons of futures, though for many apps you could widen the resolution quite a bit more than that even.)
In addition to the Akka patch in the above link, I did some benchmarking here: https://github.com/havocp/expiration
Results from my laptop are:
BatchDrained maxBatch= 100 11.259 ms/item trimmed avg (11.176 median 22.421 untrimmed 8.326 min) over 500 iterations
BatchDrained maxBatch=1000 10.538 ms/item trimmed avg (10.521 median 13.698 untrimmed 7.416 min) over 500 iterations
BatchDrained maxBatch=5000 10.875 ms/item trimmed avg (10.691 median 11.395 untrimmed 7.427 min) over 500 iterations
ConcurrentLinkedQueue 14.992 ms/item trimmed avg (14.371 median 16.142 untrimmed 9.380 min) over 500 iterations
scheduleOnce per expirable 199.469 ms/item trimmed avg (199.938 median 197.054 untrimmed 148.334 min) over 50 iterations
The "item" that takes 11.259ms is queuing and expiring 100,000 objects immediately, so measuring pure book-keeping overhead. Doing the math, that is order of 100 nanoseconds time overhead per-future for either a custom batch-drain thing, or a simple concurrent queue solution as in the patch at https://github.com/havocp/akka/commits/timeouts-as-exceptions ; memory overhead for the concurrent queue is a linked list node, for the batch drain it's an array slot plus some other stuff amortized over the entire batch. Time overhead for a scheduleOnce per-future is order of 2000ns on my laptop, plus a fair memory overhead.
My thought is that the batch drain may not be worth the extra complexity but the concurrent queue solution is simple and pretty fast. Other solutions may be even better!
There is room for more micro-optimization, there's some debug stuff and virtualization that doesn't need to be there, and the function that rechecks the time remaining and reschedules batches could probably be redone to generate fewer intermediate objects and go faster. That function (clearBatch/recheck) seems to be the bottleneck on my laptop.
Pasting in my thoughts from the list, several things break if onComplete is not guaranteed to be called (if expiration is not a completion):
- composition of timeout, i.e. if you time out inside a future that an outer future depends on, the other future should be immediately timed out regardless of its own timeout, but it is not. this applies to map, flatMap, a hypothetical onCompleteTell, sequence, etc.
- resource cleanup; currently to do something like close a socket, you need to always use both onComplete and onTimeout
If you make expiration complete the future, there are still very good options for people who need maximum speed and/or precise timing:
- set Timeout.never on a future, which will keep the future from using the expiration service or scheduler. Then you can do whatever by-hand custom thing you want (including never expiring the future). Timeout.never is equivalent to a "Timeout.custom" or "Timeout.manual"
- override/configure the expiration service on a dispatcher, to either have precise timing or to never expire anything, globally to the entire dispatcher.
- use await(duration), which still has precise timing since it's blocking on an atomic reference instead of relying on a callback from the scheduler.
(1% because say 50ms resolution on a 5s default timeout could get you a lot of batching for an app that's generating tons of futures, though for many apps you could widen the resolution quite a bit more than that even.)
on 2011-10-07 19:09 *
By viktorklang
Summary changed from await should complete the Future with Left(TimeoutException) if timeout happens to FUTURES: await should complete the Future with Left(TimeoutException) if timeout happens
We're going to remove timeout as a core concept, it's always possible to schedule to complete a promise with a TimeoutException
Updating tickets (#1129, #1132, #1138, #1149, #1153, #1154, #1157, #1161, #1163, #1168, #1170, #1171, #1172, #1176, #1177, #1178, #1180, #1199, #1217, #1218, #1219, #1237, #1238, #1239, #1244, #1246, #1249, #1250, #1251, #1252, #1256, #1301, #1302, #1306, #1395, #1396, #1409, #1418, #455, #891, #895, #912, #956, #972, #1031, #1374, #880, #1125, #1146)