mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 16:57:49 +03:00
d71a100be2
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
152 lines
5.4 KiB
C++
152 lines
5.4 KiB
C++
/*
|
|
* Copyright (c) 2004-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 <gtest/gtest.h>
|
|
#include "eden/fs/testharness/FakeFuse.h"
|
|
#include "eden/fs/testharness/FakeTreeBuilder.h"
|
|
#include "eden/fs/testharness/TestMount.h"
|
|
#include "eden/fs/utils/UnboundedQueueThreadPool.h"
|
|
|
|
#include <folly/io/async/EventBase.h>
|
|
#include <folly/io/async/ScopedEventBaseThread.h>
|
|
#include <folly/logging/xlog.h>
|
|
#include <folly/test/TestUtils.h>
|
|
|
|
using namespace facebook::eden;
|
|
using namespace std::chrono_literals;
|
|
using folly::Future;
|
|
using folly::ScopedEventBaseThread;
|
|
using folly::Unit;
|
|
using std::make_shared;
|
|
|
|
TEST(FuseTest, initMount) {
|
|
auto builder1 = FakeTreeBuilder();
|
|
builder1.setFile("src/main.c", "int main() { return 0; }\n");
|
|
builder1.setFile("src/test/test.c", "testy tests");
|
|
TestMount testMount{builder1};
|
|
|
|
auto fuse = make_shared<FakeFuse>();
|
|
testMount.registerFakeFuse(fuse);
|
|
|
|
auto initFuture = testMount.getEdenMount()
|
|
->startFuse()
|
|
.then([] { XLOG(INFO) << "startFuse() succeeded"; })
|
|
.onError([&](const folly::exception_wrapper& ew) {
|
|
ADD_FAILURE() << "startFuse() failed: "
|
|
<< folly::exceptionStr(ew);
|
|
});
|
|
|
|
struct fuse_init_in initArg;
|
|
initArg.major = FUSE_KERNEL_VERSION;
|
|
initArg.minor = FUSE_KERNEL_MINOR_VERSION;
|
|
initArg.max_readahead = 0;
|
|
initArg.flags = 0;
|
|
auto reqID = fuse->sendRequest(FUSE_INIT, 1, initArg);
|
|
auto response = fuse->recvResponse();
|
|
EXPECT_EQ(reqID, response.header.unique);
|
|
EXPECT_EQ(0, response.header.error);
|
|
EXPECT_EQ(
|
|
sizeof(fuse_out_header) + sizeof(fuse_init_out), response.header.len);
|
|
|
|
// Wait for the mount to complete
|
|
std::move(initFuture).get(250ms);
|
|
|
|
// Close the FakeFuse device, and ensure that the mount's FUSE completion
|
|
// future is then signalled.
|
|
fuse->close();
|
|
auto mountInfo =
|
|
testMount.getEdenMount()->getFuseCompletionFuture().get(100ms);
|
|
|
|
// Since we closed the FUSE device from the "kernel" side the returned
|
|
// MountInfo should not contain a valid FUSE device any more.
|
|
EXPECT_FALSE(mountInfo.fuseFD);
|
|
}
|
|
|
|
// Test destroying the EdenMount object while FUSE initialization is still
|
|
// pending
|
|
TEST(FuseTest, destroyBeforeInitComplete) {
|
|
auto builder1 = FakeTreeBuilder();
|
|
builder1.setFile("src/main.c", "int main() { return 0; }\n");
|
|
builder1.setFile("src/test/test.c", "testy tests");
|
|
|
|
auto fuse = make_shared<FakeFuse>();
|
|
auto initFuture = Future<Unit>::makeEmpty();
|
|
{
|
|
// Create the TestMountj
|
|
TestMount testMount{builder1};
|
|
testMount.registerFakeFuse(fuse);
|
|
|
|
// Call startFuse() on the test mount.
|
|
initFuture = testMount.getEdenMount()->startFuse();
|
|
|
|
// Exit the scope to destroy the mount
|
|
}
|
|
|
|
// The initFuture() should have completed unsuccessfully.
|
|
EXPECT_THROW_RE(
|
|
std::move(initFuture).get(100ms),
|
|
std::runtime_error,
|
|
"FuseChannel for .* stopped while waiting for INIT packet");
|
|
}
|
|
|
|
// Test destroying the EdenMount object immediately after the FUSE INIT request
|
|
// has been received. We previously had some race conditions that could cause
|
|
// problems here.
|
|
TEST(FuseTest, destroyWithInitRace) {
|
|
auto builder1 = FakeTreeBuilder();
|
|
builder1.setFile("src/main.c", "int main() { return 0; }\n");
|
|
builder1.setFile("src/test/test.c", "testy tests");
|
|
|
|
auto fuse = make_shared<FakeFuse>();
|
|
auto initFuture = Future<Unit>::makeEmpty();
|
|
auto completionFuture = Future<TakeoverData::MountInfo>::makeEmpty();
|
|
{
|
|
// Create the TestMountj
|
|
TestMount testMount{builder1};
|
|
testMount.registerFakeFuse(fuse);
|
|
|
|
// Call startFuse() on the test mount.
|
|
initFuture = testMount.getEdenMount()->startFuse();
|
|
completionFuture = testMount.getEdenMount()->getFuseCompletionFuture();
|
|
|
|
// Send the FUSE INIT request.
|
|
struct fuse_init_in initArg;
|
|
initArg.major = FUSE_KERNEL_VERSION;
|
|
initArg.minor = FUSE_KERNEL_MINOR_VERSION;
|
|
initArg.max_readahead = 0;
|
|
initArg.flags = 0;
|
|
auto reqID = fuse->sendRequest(FUSE_INIT, 1, initArg);
|
|
|
|
// Wait to receive the INIT reply from the FuseChannel code to confirm
|
|
// that it saw the INIT request.
|
|
auto response = fuse->recvResponse();
|
|
EXPECT_EQ(reqID, response.header.unique);
|
|
EXPECT_EQ(0, response.header.error);
|
|
EXPECT_EQ(
|
|
sizeof(fuse_out_header) + sizeof(fuse_init_out), response.header.len);
|
|
|
|
// Exit the scope to destroy the TestMount.
|
|
// This will call EdenMount::destroy() to start destroying the EdenMount.
|
|
// However, this may not complete immediately. Previously we had a bug
|
|
// where the ServerState object was not guaranteed to survive until the
|
|
// EdenMount was completely destroyed in this case.
|
|
}
|
|
|
|
// The initFuture should complete successfully, since we know that
|
|
// FuseChannel sent the INIT response.
|
|
std::move(initFuture).get(250ms);
|
|
// The FUSE completion future should also be signalled when the FuseChannel
|
|
// is destroyed.
|
|
auto mountInfo = std::move(completionFuture).get(250ms);
|
|
// Since we just destroyed the EdenMount and the kernel-side of the FUSE
|
|
// channel was not stopped the returned MountInfo should contain the FUSE
|
|
// device.
|
|
EXPECT_TRUE(mountInfo.fuseFD);
|
|
}
|