// Checks that the 'after' callback gets executed if the future is not // completed. TEST(FutureTest, After1) { Clock::pause(); std::atomic_bool executed(false); Future<Nothing> future = Future<Nothing>() .after(Hours(42), lambda::bind(&after, &executed, lambda::_1)); // A pending future should stay pending until 'after' is executed. EXPECT_TRUE(future.isPending()); // Only advanced halfway, future should remain pending. Clock::advance(Hours(21)); EXPECT_TRUE(future.isPending()); // Even doing a discard on the future should keep it pending. future.discard(); EXPECT_TRUE(future.isPending()); // After advancing all the way the future should now fail because // the 'after' callback gets executed. Clock::advance(Hours(21)); AWAIT_FAILED(future); EXPECT_TRUE(executed.load()); Clock::resume(); }
TEST(IO, Read) { ASSERT_TRUE(GTEST_IS_THREADSAFE); int pipes[2]; char data[3]; // Create a blocking pipe. ASSERT_NE(-1, ::pipe(pipes)); // Test on a blocking file descriptor. AWAIT_EXPECT_FAILED(io::read(pipes[0], data, 3)); close(pipes[0]); close(pipes[1]); // Test on a closed file descriptor. AWAIT_EXPECT_FAILED(io::read(pipes[0], data, 3)); // Create a nonblocking pipe. ASSERT_NE(-1, ::pipe(pipes)); ASSERT_SOME(os::nonblock(pipes[0])); ASSERT_SOME(os::nonblock(pipes[1])); // Test reading nothing. AWAIT_EXPECT_FAILED(io::read(pipes[0], data, 0)); // Test successful read. Future<size_t> future = io::read(pipes[0], data, 3); ASSERT_FALSE(future.isReady()); ASSERT_EQ(2, write(pipes[1], "hi", 2)); AWAIT_ASSERT_EQ(2u, future); EXPECT_EQ('h', data[0]); EXPECT_EQ('i', data[1]); // Test cancellation. future = io::read(pipes[0], data, 1); ASSERT_FALSE(future.isReady()); future.discard(); ASSERT_EQ(3, write(pipes[1], "omg", 3)); AWAIT_ASSERT_EQ(3u, io::read(pipes[0], data, 3)); EXPECT_EQ('o', data[0]); EXPECT_EQ('m', data[1]); EXPECT_EQ('g', data[2]); // Test read EOF. future = io::read(pipes[0], data, 3); ASSERT_FALSE(future.isReady()); close(pipes[1]); AWAIT_ASSERT_EQ(0u, future); close(pipes[0]); }
TEST(AwaitTest, AwaitSingleDiscard) { Promise<int> promise; auto bar = [&]() { return promise.future(); }; auto foo = [&]() { return await(bar()) .then([](const Future<int>& f) { return f .then([](int i) { return stringify(i); }); }); }; Future<string> future = foo(); future.discard(); AWAIT_DISCARDED(future); EXPECT_TRUE(promise.future().hasDiscard()); }
TEST(BasicMasterContenderDetectorTest, Detector) { PID<Master> master; master.node.ip = 10000000; master.node.port = 10000; StandaloneMasterDetector detector; Future<Option<MasterInfo> > detected = detector.detect(); // No one has appointed the leader so we are pending. EXPECT_TRUE(detected.isPending()); // Ensure that the future can be discarded. detected.discard(); AWAIT_DISCARDED(detected); detected = detector.detect(); // Still no leader appointed yet. EXPECT_TRUE(detected.isPending()); detector.appoint(master); AWAIT_READY(detected); }
TEST(FutureTest, Select) { Promise<int> promise1; Promise<int> promise2; Promise<int> promise3; Promise<int> promise4; std::set<Future<int>> futures = { promise1.future(), promise2.future(), promise3.future(), promise4.future() }; promise1.set(42); Future<Future<int>> future = select(futures); AWAIT_READY(future); AWAIT_READY(future.get()); EXPECT_EQ(42, future->get()); futures.erase(promise1.future()); future = select(futures); EXPECT_TRUE(future.isPending()); future.discard(); AWAIT_DISCARDED(future); }
TEST_F(DockerTest, ROOT_DOCKER_CancelPull) { // Delete the test image if it exists. Try<Subprocess> s = process::subprocess( tests::flags.docker + " rmi lingmann/1gb", Subprocess::PATH("/dev/null"), Subprocess::PATH("/dev/null"), Subprocess::PATH("/dev/null")); ASSERT_SOME(s); AWAIT_READY_FOR(s.get().status(), Seconds(30)); Owned<Docker> docker = Docker::create( tests::flags.docker, tests::flags.docker_socket, false).get(); Try<string> directory = environment->mkdtemp(); CHECK_SOME(directory) << "Failed to create temporary directory"; // Assume that pulling the very large image 'lingmann/1gb' will take // sufficiently long that we can start it and discard (i.e., cancel // it) right away and the future will indeed get discarded. Future<Docker::Image> future = docker->pull(directory.get(), "lingmann/1gb"); future.discard(); AWAIT_DISCARDED(future); }
// A single contender gets elected automatically. TEST_F(ZooKeeperMasterContenderDetectorTest, MasterContender) { Try<zookeeper::URL> url = zookeeper::URL::parse( "zk://" + server->connectString() + "/mesos"); ASSERT_SOME(url); Owned<zookeeper::Group> group( new Group(url.get(), MASTER_CONTENDER_ZK_SESSION_TIMEOUT)); ZooKeeperMasterContender* contender = new ZooKeeperMasterContender(group); PID<Master> pid; pid.node.ip = 10000000; pid.node.port = 10000; MasterInfo master = internal::protobuf::createMasterInfo(pid); contender->initialize(master); Future<Future<Nothing> > contended = contender->contend(); AWAIT_READY(contended); ZooKeeperMasterDetector detector(url.get()); Future<Option<MasterInfo> > leader = detector.detect(); AWAIT_READY(leader); EXPECT_SOME_EQ(master, leader.get()); leader = detector.detect(leader.get()); // No change to leadership. ASSERT_TRUE(leader.isPending()); // Ensure we can discard the future. leader.discard(); AWAIT_DISCARDED(leader); // After the discard, we can re-detect correctly. leader = detector.detect(None()); AWAIT_READY(leader); EXPECT_SOME_EQ(master, leader.get()); // Now test that a session expiration causes candidacy to be lost // and the future to become ready. Future<Nothing> lostCandidacy = contended.get(); leader = detector.detect(leader.get()); Future<Option<int64_t> > sessionId = group.get()->session(); AWAIT_READY(sessionId); server->expireSession(sessionId.get().get()); AWAIT_READY(lostCandidacy); AWAIT_READY(leader); EXPECT_NONE(leader.get()); }
// Tests that we don't propagate a discard through a `recover()` but a // discard can still be called and propagate later. TEST(FutureTest, RecoverDiscard) { Promise<int> promise1; Promise<string> promise2; Promise<string> promise3; Promise<string> promise4; Future<string> future = promise1.future() .then([]() -> string { return "hello"; }) .recover([&](const Future<string>&) { return promise2.future() .then([&]() { return promise3.future() .then([&]() { return promise4.future(); }); }); }); future.discard(); promise1.discard(); EXPECT_FALSE(promise2.future().hasDiscard()); promise2.set(string("not world")); EXPECT_FALSE(promise3.future().hasDiscard()); promise3.set(string("also not world")); EXPECT_FALSE(promise4.future().hasDiscard()); future.discard(); EXPECT_TRUE(promise4.future().hasDiscard()); promise4.set(string("world")); AWAIT_EQ("world", future); }
/* * Class: org_apache_mesos_state_AbstractState * Method: __expunge_cancel * Signature: (J)Z */ JNIEXPORT jboolean JNICALL Java_org_apache_mesos_state_AbstractState__1_1expunge_1cancel (JNIEnv* env, jobject thiz, jlong jfuture) { Future<bool>* future = (Future<bool>*) jfuture; // We'll initiate a discard but we won't consider it cancelled since // we don't know if/when the future will get discarded. future->discard(); return (jboolean) false; }
/* * Class: org_apache_mesos_state_ZooKeeperState * Method: __names_cancel * Signature: (J)Z */ JNIEXPORT jboolean JNICALL Java_org_apache_mesos_state_ZooKeeperState__1_1names_1cancel (JNIEnv* env, jobject thiz, jlong jfuture) { Future<vector<string> >* future = (Future<vector<string> >*) jfuture; if (!future->isDiscarded()) { future->discard(); return (jboolean) future->isDiscarded(); } return (jboolean) true; }
void timedout() { // Need to discard all of the futures so any of their associated // resources can get properly cleaned up. typename std::list<Future<T> >::const_iterator iterator; for (iterator = futures.begin(); iterator != futures.end(); ++iterator) { Future<T> future = *iterator; // Need a non-const copy to discard. future.discard(); } promise->fail("Collect failed: timed out"); terminate(this); }
TEST(FutureTest, UndiscardableFuture) { Promise<int> promise; Future<int> f = undiscardable(promise.future()); f.discard(); EXPECT_TRUE(f.hasDiscard()); EXPECT_FALSE(promise.future().hasDiscard()); promise.set(42); AWAIT_ASSERT_EQ(42, f); }
TEST(CollectTest, DiscardPropagation) { Future<int> future1; Future<bool> future2; future1 .onDiscard([=](){ process::internal::discarded(future1); }); future2 .onDiscard([=](){ process::internal::discarded(future2); }); Future<std::tuple<int, bool>> collect = process::collect(future1, future2); collect.discard(); AWAIT_DISCARDED(future1); AWAIT_DISCARDED(future2); }
TEST(AwaitTest, DiscardPropagation) { Future<int> future1; Future<bool> future2; future1 .onDiscard([=](){ process::internal::discarded(future1); }); future2 .onDiscard([=](){ process::internal::discarded(future2); }); Future<std::tuple<Future<int>, Future<bool>>> await = process::await(future1, future2); await.discard(); AWAIT_DISCARDED(future1); AWAIT_DISCARDED(future2); }
TEST(FutureTest, UndiscardableLambda) { Promise<int> promise; Future<int> f = Future<int>(2) .then(undiscardable([&](int multiplier) { return promise.future() .then([=](int i) { return i * multiplier; }); })); f.discard(); EXPECT_TRUE(f.hasDiscard()); EXPECT_FALSE(promise.future().hasDiscard()); promise.set(42); AWAIT_ASSERT_EQ(84, f); }
TEST(LoopTest, DiscardIterate) { Promise<int> promise; promise.future().onDiscard([&]() { promise.discard(); }); Future<Nothing> future = loop( [&]() { return promise.future(); }, [&](int i) -> ControlFlow<Nothing> { return Break(); }); EXPECT_TRUE(future.isPending()); future.discard(); AWAIT_DISCARDED(future); EXPECT_TRUE(promise.future().hasDiscard()); }
TEST(AwaitTest, DiscardPropagation) { Promise<int> promise1; Promise<bool> promise2; promise1.future() .onDiscard([&](){ promise1.discard(); }); promise2.future() .onDiscard([&](){ promise2.discard(); }); Future<std::tuple<Future<int>, Future<bool>>> await = process::await( promise1.future(), promise2.future()); await.discard(); AWAIT_DISCARDED(await); AWAIT_DISCARDED(promise1.future()); AWAIT_DISCARDED(promise2.future()); }
TEST(CollectTest, DiscardPropagation) { Promise<int> promise1; Promise<bool> promise2; promise1.future() .onDiscard([&](){ promise1.discard(); }); promise2.future() .onDiscard([&](){ promise2.discard(); }); Future<std::tuple<int, bool>> collect = process::collect( promise1.future(), promise2.future()); collect.discard(); AWAIT_DISCARDED(collect); AWAIT_DISCARDED(promise1.future()); AWAIT_DISCARDED(promise2.future()); }
TEST(IOTest, Poll) { ASSERT_TRUE(GTEST_IS_THREADSAFE); int pipes[2]; ASSERT_NE(-1, pipe(pipes)); // Test discard when polling. Future<short> future = io::poll(pipes[0], io::READ); EXPECT_TRUE(future.isPending()); future.discard(); AWAIT_DISCARDED(future); // Test successful polling. future = io::poll(pipes[0], io::READ); EXPECT_TRUE(future.isPending()); ASSERT_EQ(3, write(pipes[1], "hi", 3)); AWAIT_EXPECT_EQ(io::READ, future); ASSERT_SOME(os::close(pipes[0])); ASSERT_SOME(os::close(pipes[1])); }
// Tests that Future::discard does not complete the future and // Promise::set can still be invoked to complete the future. TEST(FutureTest, Discard2) { Promise<bool> promise1; Promise<int> promise2; std::atomic_bool executed(false); Future<int> future = Future<string>("hello world") .then(lambda::bind(&inner1, promise1.future())) .then(lambda::bind(&inner2, &executed, promise2.future())); ASSERT_TRUE(future.isPending()); future.discard(); // The future should remain pending, even though we discarded it. ASSERT_TRUE(future.hasDiscard()); ASSERT_TRUE(future.isPending()); // The future associated with the lambda already executed in the // first 'then' should have the discard propagated to it. ASSERT_TRUE(promise1.future().hasDiscard()); // But the future assocaited with the lambda that hasn't yet been // executed should not have the discard propagated to it. ASSERT_FALSE(promise2.future().hasDiscard()); // Now setting the promise should cause the outer future to be // discarded rather than executing the last lambda because the // implementation of Future::then does not continue the chain once a // discard occurs. ASSERT_TRUE(promise1.set(true)); AWAIT_DISCARDED(future); // And the final lambda should never have executed. ASSERT_FALSE(executed.load()); ASSERT_TRUE(promise2.future().isPending()); }
// Tests that Future::discard does not complete the future and // Promise::fail can still be invoked to complete the future. TEST(FutureTest, Discard3) { Promise<bool> promise1; Promise<int> promise2; std::atomic_bool executed(false); Future<int> future = Future<string>("hello world") .then(lambda::bind(&inner1, promise1.future())) .then(lambda::bind(&inner2, &executed, promise2.future())); ASSERT_TRUE(future.isPending()); future.discard(); // The future should remain pending, even though we discarded it. ASSERT_TRUE(future.hasDiscard()); ASSERT_TRUE(future.isPending()); // The future associated with the lambda already executed in the // first 'then' should have the discard propagated to it. ASSERT_TRUE(promise1.future().hasDiscard()); // But the future assocaited with the lambda that hasn't yet been // executed should not have the discard propagated to it. ASSERT_FALSE(promise2.future().hasDiscard()); // Now failing the promise should cause the outer future to be // failed also. ASSERT_TRUE(promise1.fail("failure message")); AWAIT_FAILED(future); // And the final lambda should never have executed. ASSERT_FALSE(executed.load()); ASSERT_TRUE(promise2.future().isPending()); }
// Checks that completing a promise will keep the 'after' callback // from executing. TEST(FutureTest, After2) { Clock::pause(); std::atomic_bool executed(false); Promise<Nothing> promise; Future<Nothing> future = promise.future() .after(Hours(42), lambda::bind(&after, &executed, lambda::_1)); EXPECT_TRUE(future.isPending()); // Only advanced halfway, future should remain pending. Clock::advance(Hours(21)); EXPECT_TRUE(future.isPending()); // Even doing a discard on the future should keep it pending. future.discard(); EXPECT_TRUE(future.isPending()); // Now set the promise, the 'after' timer should be cancelled and // the pending future should be completed. promise.set(Nothing()); AWAIT_READY(future); // Advancing time the rest of the way should not cause the 'after' // callback to execute. Clock::advance(Hours(21)); EXPECT_FALSE(executed.load()); Clock::resume(); }
Future<Response> RegistryClientProcess::doHttpGet( const URL& url, const Option<process::http::Headers>& headers, const Duration& timeout, bool resend, const Option<string>& lastResponseStatus) const { return process::http::get(url, headers) .after(timeout, []( const Future<Response>& httpResponseFuture) -> Future<Response> { return Failure("Response timeout"); }) .then(defer(self(), [=]( const Response& httpResponse) -> Future<Response> { VLOG(1) << "Response status: " + httpResponse.status; // Set the future if we get a OK response. if (httpResponse.status == "200 OK") { return httpResponse; } else if (httpResponse.status == "400 Bad Request") { Try<JSON::Object> errorResponse = JSON::parse<JSON::Object>(httpResponse.body); if (errorResponse.isError()) { return Failure("Failed to parse bad request response JSON: " + errorResponse.error()); } std::ostringstream out; bool first = true; Result<JSON::Array> errorObjects = errorResponse.get().find<JSON::Array>("errors"); if (errorObjects.isError()) { return Failure("Failed to find 'errors' in bad request response: " + errorObjects.error()); } else if (errorObjects.isNone()) { return Failure("Errors not found in bad request response"); } foreach (const JSON::Value& error, errorObjects.get().values) { Result<JSON::String> message = error.as<JSON::Object>().find<JSON::String>("message"); if (message.isError()) { return Failure("Failed to parse bad request error message: " + message.error()); } else if (message.isNone()) { continue; } if (first) { out << message.get().value; first = false; } else { out << ", " << message.get().value; } } return Failure("Received Bad request, errors: [" + out.str() + "]"); } // Prevent infinite recursion. if (lastResponseStatus.isSome() && (lastResponseStatus.get() == httpResponse.status)) { return Failure("Invalid response: " + httpResponse.status); } // If resend is not set, we dont try again and stop here. if (!resend) { return Failure("Bad response: " + httpResponse.status); } // Handle 401 Unauthorized. if (httpResponse.status == "401 Unauthorized") { Try<process::http::Headers> authAttributes = getAuthenticationAttributes(httpResponse); if (authAttributes.isError()) { return Failure( "Failed to get authentication attributes: " + authAttributes.error()); } // TODO(jojy): Currently only handling TLS/cert authentication. Future<Token> tokenResponse = tokenManager_->getToken( authAttributes.get().at("service"), authAttributes.get().at("scope"), None()); return tokenResponse .after(timeout, [=]( Future<Token> tokenResponse) -> Future<Token> { tokenResponse.discard(); return Failure("Token response timeout"); }) .then(defer(self(), [=]( const Future<Token>& tokenResponse) { // Send request with acquired token. process::http::Headers authHeaders = { {"Authorization", "Bearer " + tokenResponse.get().raw} }; return doHttpGet( url, authHeaders, timeout, true, httpResponse.status); })); } else if (httpResponse.status == "307 Temporary Redirect") { // Handle redirect. // TODO(jojy): Add redirect functionality in http::get. auto toURL = []( const string& urlString) -> Try<URL> { // TODO(jojy): Need to add functionality to URL class that parses a // string to its URL components. For now, assuming: // - scheme is https // - path always ends with / static const string schemePrefix = "https://"; if (!strings::contains(urlString, schemePrefix)) { return Error( "Failed to find expected token '" + schemePrefix + "' in redirect url"); } const string schemeSuffix = urlString.substr(schemePrefix.length()); const vector<string> components = strings::tokenize(schemeSuffix, "/"); const string path = schemeSuffix.substr(components[0].length()); const vector<string> addrComponents = strings::tokenize(components[0], ":"); uint16_t port = DEFAULT_SSL_PORT; string domain = components[0]; // Parse the port. if (addrComponents.size() == 2) { domain = addrComponents[0]; Try<uint16_t> tryPort = numify<uint16_t>(addrComponents[1]); if (tryPort.isError()) { return Error( "Failed to parse location: " + urlString + " for port."); } port = tryPort.get(); } return URL("https", domain, port, path); }; if (httpResponse.headers.find("Location") == httpResponse.headers.end()) { return Failure( "Invalid redirect response: 'Location' not found in headers."); } const string& location = httpResponse.headers.at("Location"); Try<URL> tryUrl = toURL(location); if (tryUrl.isError()) { return Failure( "Failed to parse '" + location + "': " + tryUrl.error()); } return doHttpGet( tryUrl.get(), headers, timeout, false, httpResponse.status); } else { return Failure("Invalid response: " + httpResponse.status); } }));
Future<Option<Variable<Slaves> > > timeout( Future<Option<Variable<Slaves> > > future) { future.discard(); return Failure("Timeout"); }
TEST(IOTest, Peek) { ASSERT_TRUE(GTEST_IS_THREADSAFE); int sockets[2]; int pipes[2]; char data[3] = {}; // Create a blocking socketpair. ASSERT_NE(-1, ::socketpair(PF_LOCAL, SOCK_STREAM, 0, sockets)); // Test on closed socket. ASSERT_SOME(os::close(sockets[0])); ASSERT_SOME(os::close(sockets[1])); AWAIT_EXPECT_FAILED(io::peek(sockets[0], data, sizeof(data), sizeof(data))); // Test on pipe. ASSERT_NE(-1, ::pipe(pipes)); AWAIT_EXPECT_FAILED(io::peek(pipes[0], data, sizeof(data), sizeof(data))); ASSERT_SOME(os::close(pipes[0])); ASSERT_SOME(os::close(pipes[1])); // Create a non-blocking socketpair. ASSERT_NE(-1, ::socketpair(PF_LOCAL, SOCK_STREAM, 0, sockets)); ASSERT_SOME(os::nonblock(sockets[0])); ASSERT_SOME(os::nonblock(sockets[1])); // Test peeking nothing. AWAIT_EXPECT_EQ(0, io::peek(sockets[0], data, 0, 0)); // Test discarded peek. Future<size_t> future = io::peek(sockets[0], data, sizeof(data), 1); EXPECT_TRUE(future.isPending()); future.discard(); AWAIT_DISCARDED(future); // Test successful peek. future = io::peek(sockets[0], data, sizeof(data), 2); ASSERT_FALSE(future.isReady()); ASSERT_EQ(2, write(sockets[1], "hi", 2)); AWAIT_ASSERT_EQ(2u, future); EXPECT_EQ('h', data[0]); EXPECT_EQ('i', data[1]); // Discard what was read before and peek again. memset(data, 0, sizeof(data)); future = io::peek(sockets[0], data, sizeof(data), 2); ASSERT_TRUE(future.isReady()); AWAIT_ASSERT_EQ(2u, future); EXPECT_EQ('h', data[0]); EXPECT_EQ('i', data[1]); // Discard what was read before and now io::read. memset(data, 0, sizeof(data)); future = io::read(sockets[0], data, sizeof(data)); ASSERT_TRUE(future.isReady()); AWAIT_ASSERT_EQ(2u, future); EXPECT_EQ('h', data[0]); EXPECT_EQ('i', data[1]); // Test read EOF. future = io::peek(sockets[0], data, sizeof(data), 2); ASSERT_FALSE(future.isReady()); ASSERT_SOME(os::close(sockets[1])); AWAIT_ASSERT_EQ(0u, future); ASSERT_SOME(os::close(sockets[0])); // Test the auxiliary interface. ASSERT_NE(-1, ::socketpair(PF_LOCAL, SOCK_STREAM, 0, sockets)); ASSERT_SOME(os::nonblock(sockets[0])); ASSERT_SOME(os::nonblock(sockets[1])); // Test exceeding read buffer size limit. AWAIT_EXPECT_FAILED(io::peek(sockets[0], io::BUFFERED_READ_SIZE + 1)); // The function should return after reading some data (not // necessarily as much as we expect). We test that by writing less // than we expect to read. Future<string> result = io::peek(sockets[0], 4); EXPECT_TRUE(result.isPending()); ASSERT_EQ(2, write(sockets[1], "Hi", 2)); AWAIT_ASSERT_EQ("Hi", result); ASSERT_SOME(os::close(sockets[0])); ASSERT_SOME(os::close(sockets[1])); }