sapling/eden/fs/utils/test/IoFutureTest.cpp
Marshall Cline d71a100be2 use rvalue-qual Future::get(): pass 5
Summary:
This is part of "the great r-valuification of folly::Future":

* This is something we should do for safety in general.
* Context: `Future::get(...)` means both `Future::get()` and `Future::get(Duration)`
* Using lvalue-qualified `Future::get(...)` has caused some failures around D7840699 since lvalue-qualification hides that operation's move-out semantics - leads to some use of future operations that are really not correct, but are not obviously incorrect.
* Problems with `Future::get(...) &`: it moves-out the result but doesn't invalidate the Future - the Future remains (technically) valid even though it actually is partially moved-out. Callers can subsequently access that moved-out result via things like `future.get(...)`, `future.result()`, `future.value()`, etc. - these access an already-moved-out result which is/can be surprising.
* Reasons `Future::get(...) &&` is better: its semantics are more obvious and user-testable. It moves-out the Future, leaving it with `future.valid() == false`.

Reviewed By: yfeldblum

Differential Revision: D8711368

fbshipit-source-id: fbfcb731097cdf9d8d98583956bc7fe614157a6b
2018-07-02 07:05:52 -07:00

120 lines
3.7 KiB
C++

/*
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include "eden/fs/utils/IoFuture.h"
#include <folly/Exception.h>
#include <folly/File.h>
#include <folly/io/async/EventBase.h>
#include <folly/test/TestUtils.h>
#include <gtest/gtest.h>
#include <sys/socket.h>
#include <chrono>
using facebook::eden::IoFuture;
using facebook::eden::waitForIO;
using folly::checkUnixError;
using folly::EventBase;
using folly::EventHandler;
using namespace std::chrono_literals;
namespace {
std::pair<folly::File, folly::File> createSocketPair() {
std::array<int, 2> sockets;
int rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets.data());
checkUnixError(rc, "socketpair failed");
return std::make_pair(
folly::File{sockets[0], true}, folly::File{sockets[1], true});
}
} // namespace
TEST(IoFuture, read) {
auto sockets = createSocketPair();
EventBase evb;
// Wait for READ readiness
auto f = waitForIO(&evb, sockets.first.fd(), EventHandler::READ, 1s);
evb.loopOnce();
EXPECT_FALSE(f.isReady());
auto bytesSent = send(sockets.second.fd(), "foo", 3, 0);
checkUnixError(bytesSent, "send failed");
EXPECT_FALSE(f.isReady());
evb.loopOnce();
EXPECT_TRUE(f.isReady());
}
TEST(IoFuture, readTimeout) {
auto sockets = createSocketPair();
EventBase evb;
// Wait for READ readiness
auto f = waitForIO(&evb, sockets.first.fd(), EventHandler::READ, 10ms)
.ensure([&evb] { evb.terminateLoopSoon(); });
evb.loopForever();
ASSERT_TRUE(f.isReady());
EXPECT_THROW_ERRNO(std::move(f).get(), ETIMEDOUT);
}
TEST(IoFuture, multiRead) {
auto sockets = createSocketPair();
EventBase evb;
// Re-use the same IoFuture object for multiple reads.
IoFuture iof{&evb, sockets.first.fd()};
// Wait for writability. This should be immediately ready.
auto writeF = iof.wait(EventHandler::WRITE, 1s);
evb.loopOnce();
EXPECT_TRUE(writeF.isReady());
EXPECT_EQ(EventHandler::WRITE, std::move(writeF).get());
// Wait for readability.
auto readF1 = iof.wait(EventHandler::READ, 1s);
EXPECT_FALSE(readF1.isReady());
auto bytesSent = send(sockets.second.fd(), "foo", 3, 0);
checkUnixError(bytesSent, "send failed");
evb.loopOnce();
EXPECT_TRUE(readF1.isReady());
EXPECT_EQ(EventHandler::READ, readF1.wait().value());
EXPECT_FALSE(readF1.hasException());
// Read the data so the socket no longer has read data pending.
std::array<char, 8> buf;
auto bytesRead =
recv(sockets.first.fd(), buf.data(), buf.size(), MSG_DONTWAIT);
EXPECT_EQ(bytesRead, 3);
// Wait for readability again, but expect it to time out this time.
auto readF2 = iof.wait(EventHandler::READ, 20ms);
EXPECT_FALSE(readF2.isReady());
evb.loopOnce();
ASSERT_TRUE(readF2.isReady());
EXPECT_THROW_ERRNO(std::move(readF2).get(), ETIMEDOUT);
// Try calling iof.wait() twice in a row, even though the
// first one did not finish. This should fail the earlier future with an
// ECANCELED error.
auto readF3 = iof.wait(EventHandler::READ, 1s);
EXPECT_FALSE(readF3.isReady());
auto readF4 = iof.wait(EventHandler::READ, 1s);
ASSERT_TRUE(readF3.isReady());
EXPECT_THROW_ERRNO(std::move(readF3).get(), ECANCELED);
EXPECT_FALSE(readF4.isReady());
bytesSent = send(sockets.second.fd(), "bar", 3, 0);
checkUnixError(bytesSent, "send failed");
evb.loopOnce();
ASSERT_TRUE(readF4.isReady());
EXPECT_EQ(EventHandler::READ, std::move(readF4).get());
bytesRead = recv(sockets.first.fd(), buf.data(), buf.size(), MSG_DONTWAIT);
EXPECT_EQ(bytesRead, 3);
}