From: Hannes Roth Date: Wed, 15 Jul 2015 23:39:48 +0000 (-0700) Subject: (Wangle) within should raise TimedOut() X-Git-Tag: v0.51.0~10 X-Git-Url: http://plrg.eecs.uci.edu/git/?a=commitdiff_plain;h=65fd92b6e94d1c66d3499871282b516b1e412eb0;p=folly.git (Wangle) within should raise TimedOut() Summary: I figured it out. This works. The two extra futures are a small overhead (just two pointers). The `Core`s are allocated anyway, so this is pretty much optimal. A timeout now raises on the current Future, and a fulfilled promise cancels the timeout Future. Reviewed By: @yfeldblum Differential Revision: D2232463 --- diff --git a/folly/futures/Future-inl.h b/folly/futures/Future-inl.h index 380b0069..d79fea34 100644 --- a/folly/futures/Future-inl.h +++ b/folly/futures/Future-inl.h @@ -855,34 +855,41 @@ template Future Future::within(Duration dur, E e, Timekeeper* tk) { struct Context { - Context(E ex) : exception(std::move(ex)), promise() {} + Context(E ex, Future&& f) + : exception(std::move(ex)), afterFuture(std::move(f)), promise() {} E exception; + Future afterFuture; + Future thisFuture; Promise promise; std::atomic token {false}; }; - auto ctx = std::make_shared(std::move(e)); if (!tk) { tk = folly::detail::getTimekeeperSingleton(); } - tk->after(dur) - .then([ctx](Try const& t) { - if (ctx->token.exchange(true) == false) { - if (t.hasException()) { - ctx->promise.setException(std::move(t.exception())); - } else { - ctx->promise.setException(std::move(ctx->exception)); - } - } - }); + auto ctx = std::make_shared(std::move(e), tk->after(dur)); - this->then([ctx](Try&& t) { + ctx->thisFuture = this->then([ctx](Try&& t) mutable { + // "this" completed first, cancel "after" + ctx->afterFuture.raise(CancelTimer()); if (ctx->token.exchange(true) == false) { ctx->promise.setTry(std::move(t)); } }); + ctx->afterFuture.then([ctx](Try const& t) mutable { + // "after" completed first, cancel "this" + ctx->thisFuture.raise(TimedOut()); + if (ctx->token.exchange(true) == false) { + if (t.hasException()) { + ctx->promise.setException(std::move(t.exception())); + } else { + ctx->promise.setException(std::move(ctx->exception)); + } + } + }); + return ctx->promise.getFuture().via(getExecutor()); } diff --git a/folly/futures/FutureException.h b/folly/futures/FutureException.h index 1e004dcf..c5dbf158 100644 --- a/folly/futures/FutureException.h +++ b/folly/futures/FutureException.h @@ -91,6 +91,11 @@ class TimedOut : public FutureException { TimedOut() : FutureException("Timed out") {} }; +class CancelTimer : public FutureException { + public: + CancelTimer() : FutureException("Timer should be cancelled") {} +}; + class PredicateDoesNotObtain : public FutureException { public: PredicateDoesNotObtain() : FutureException("Predicate does not obtain") {} diff --git a/folly/futures/test/InterruptTest.cpp b/folly/futures/test/InterruptTest.cpp index 99f8b696..7d34106f 100644 --- a/folly/futures/test/InterruptTest.cpp +++ b/folly/futures/test/InterruptTest.cpp @@ -18,6 +18,7 @@ #include #include +#include using namespace folly; @@ -72,3 +73,44 @@ TEST(Interrupt, secondInterruptNoop) { f.cancel(); EXPECT_EQ(1, count); } + +TEST(Interrupt, withinTimedOut) { + Promise p; + Baton<> done; + p.setInterruptHandler([&](const exception_wrapper& e) { done.post(); }); + p.getFuture().within(std::chrono::milliseconds(1)); + // Give it 100ms to time out and call the interrupt handler + auto t = std::chrono::steady_clock::now() + std::chrono::milliseconds(100); + EXPECT_TRUE(done.timed_wait(t)); +} + +class DummyTimeKeeper : public Timekeeper { + public: + explicit DummyTimeKeeper() : interrupted() {} + + Future after(Duration) override { + promise.setInterruptHandler( + [this](const exception_wrapper& e) { + EXPECT_THROW(e.throwException(), CancelTimer); + interrupted.post(); + } + ); + return promise.getFuture(); + } + + Baton<> interrupted; + + private: + Promise promise; +}; + +TEST(Interrupt, withinCancelTimer) { + DummyTimeKeeper tk; + Promise p; + Baton<> done; + p.getFuture().within(std::chrono::milliseconds(10), TimedOut(), &tk); + p.setValue(1); // this should cancel the timer + // Give it 100ms to interrupt the timer Future + auto t = std::chrono::steady_clock::now() + std::chrono::milliseconds(100); + EXPECT_TRUE(tk.interrupted.timed_wait(t)); +}