blob: 4ebd6fb00bb4adeeff3d6e8cc0c631440bebdbba [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fs/managed-vfs.h>
#include <fs/synchronous-vfs.h>
#include <fs/vfs.h>
#include <fs/vnode.h>
#include <fuchsia/io/c/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/task.h>
#include <lib/sync/completion.h>
#include <lib/zx/channel.h>
#include <zircon/assert.h>
#include <unittest/unittest.h>
#include <utility>
namespace {
class FdCountVnode : public fs::Vnode {
public:
FdCountVnode()
: fd_count_(0) {}
virtual ~FdCountVnode() {
ZX_ASSERT(fd_count_ == 0);
}
int fds() const {
return fd_count_;
}
zx_status_t Open(uint32_t, fbl::RefPtr<Vnode>* redirect) final {
fd_count_++;
return ZX_OK;
}
zx_status_t Close() final {
fd_count_--;
ZX_ASSERT(fd_count_ >= 0);
return ZX_OK;
}
bool IsDirectory() const final {
return false;
}
zx_status_t GetNodeInfo(uint32_t flags, fuchsia_io_NodeInfo* info) {
info->tag = fuchsia_io_NodeInfoTag_service;
return ZX_OK;
}
private:
int fd_count_;
};
class AsyncTearDownVnode : public FdCountVnode {
public:
AsyncTearDownVnode(sync_completion_t* completions)
: callback_(nullptr), completions_(completions) {}
~AsyncTearDownVnode() {
// C) Tear down the Vnode.
ZX_ASSERT(fds() == 0);
sync_completion_signal(&completions_[2]);
}
private:
void Sync(fs::Vnode::SyncCallback callback) final {
callback_ = std::move(callback);
thrd_t thrd;
ZX_ASSERT(thrd_create(&thrd, &AsyncTearDownVnode::SyncThread, this) == thrd_success);
thrd_detach(thrd);
}
static int SyncThread(void* arg) {
fs::Vnode::SyncCallback callback;
{
fbl::RefPtr<AsyncTearDownVnode> vn =
fbl::WrapRefPtr(reinterpret_cast<AsyncTearDownVnode*>(arg));
// A) Identify when the sync has started being processed.
sync_completion_signal(&vn->completions_[0]);
// B) Wait until the connection has been closed.
sync_completion_wait(&vn->completions_[1], ZX_TIME_INFINITE);
callback = std::move(vn->callback_);
}
callback(ZX_OK);
return 0;
}
fs::Vnode::SyncCallback callback_;
sync_completion_t* completions_;
};
bool send_sync(const zx::channel& client) {
BEGIN_HELPER;
fuchsia_io_NodeSyncRequest request;
request.hdr.txid = 5;
request.hdr.ordinal = fuchsia_io_NodeSyncOrdinal;
ASSERT_EQ(client.write(0, &request, sizeof(request), nullptr, 0), ZX_OK);
END_HELPER;
}
// Helper function which creates a VFS with a served Vnode,
// starts a sync request, and then closes the connection to the client
// in the middle of the async callback.
//
// This helps tests get ready to try handling a tricky teardown.
bool sync_start(sync_completion_t* completions, async::Loop* loop,
fbl::unique_ptr<fs::ManagedVfs>* vfs) {
BEGIN_HELPER;
*vfs = std::make_unique<fs::ManagedVfs>(loop->dispatcher());
ASSERT_EQ(loop->StartThread(), ZX_OK);
auto vn = fbl::AdoptRef(new AsyncTearDownVnode(completions));
zx::channel client;
zx::channel server;
ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
ASSERT_EQ(vn->Open(0, nullptr), ZX_OK);
ASSERT_EQ(vn->Serve(vfs->get(), std::move(server), 0), ZX_OK);
vn = nullptr;
ASSERT_TRUE(send_sync(client));
// A) Wait for sync to begin.
sync_completion_wait(&completions[0], ZX_TIME_INFINITE);
client.reset();
END_HELPER;
}
// Test a case where the VFS object is shut down outside the dispatch loop.
bool TestUnpostedTeardown() {
BEGIN_TEST;
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
sync_completion_t completions[3];
fbl::unique_ptr<fs::ManagedVfs> vfs;
ASSERT_TRUE(sync_start(completions, &loop, &vfs));
// B) Let sync complete.
sync_completion_signal(&completions[1]);
sync_completion_t* vnode_destroyed = &completions[2];
sync_completion_t shutdown_done;
vfs->Shutdown([&vnode_destroyed, &shutdown_done](zx_status_t status) {
ZX_ASSERT(status == ZX_OK);
// C) Issue an explicit shutdown, check that the Vnode has
// already torn down.
ZX_ASSERT(sync_completion_wait(vnode_destroyed, ZX_SEC(0)) == ZX_OK);
sync_completion_signal(&shutdown_done);
});
ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_SEC(3)), ZX_OK);
vfs = nullptr;
END_TEST;
}
// Test a case where the VFS object is shut down as a posted request to the
// dispatch loop.
bool TestPostedTeardown() {
BEGIN_TEST;
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
sync_completion_t completions[3];
fbl::unique_ptr<fs::ManagedVfs> vfs;
ASSERT_TRUE(sync_start(completions, &loop, &vfs));
// B) Let sync complete.
sync_completion_signal(&completions[1]);
sync_completion_t* vnode_destroyed = &completions[2];
sync_completion_t shutdown_done;
ASSERT_EQ(async::PostTask(loop.dispatcher(), [&]() {
vfs->Shutdown([&vnode_destroyed, &shutdown_done](zx_status_t status) {
ZX_ASSERT(status == ZX_OK);
// C) Issue an explicit shutdown, check that the Vnode has
// already torn down.
ZX_ASSERT(sync_completion_wait(vnode_destroyed, ZX_SEC(0)) == ZX_OK);
sync_completion_signal(&shutdown_done);
});
}),
ZX_OK);
ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_SEC(3)), ZX_OK);
vfs = nullptr;
END_TEST;
}
// Test a case where the VFS object destroyed inside the callback to Shutdown.
bool TestTeardownDeleteThis() {
BEGIN_TEST;
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
sync_completion_t completions[3];
fbl::unique_ptr<fs::ManagedVfs> vfs;
ASSERT_TRUE(sync_start(completions, &loop, &vfs));
// B) Let sync complete.
sync_completion_signal(&completions[1]);
sync_completion_t* vnode_destroyed = &completions[2];
sync_completion_t shutdown_done;
fs::ManagedVfs* raw_vfs = vfs.release();
raw_vfs->Shutdown([&raw_vfs, &vnode_destroyed, &shutdown_done](zx_status_t status) {
ZX_ASSERT(status == ZX_OK);
// C) Issue an explicit shutdown, check that the Vnode has
// already torn down.
ZX_ASSERT(sync_completion_wait(vnode_destroyed, ZX_SEC(0)) == ZX_OK);
delete raw_vfs;
sync_completion_signal(&shutdown_done);
});
ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_SEC(3)), ZX_OK);
END_TEST;
}
// Test a case where the VFS object is shut down before a background async
// callback gets the chance to complete.
bool TestTeardownSlowAsyncCallback() {
BEGIN_TEST;
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
sync_completion_t completions[3];
fbl::unique_ptr<fs::ManagedVfs> vfs;
ASSERT_TRUE(sync_start(completions, &loop, &vfs));
sync_completion_t* vnode_destroyed = &completions[2];
sync_completion_t shutdown_done;
vfs->Shutdown([&vnode_destroyed, &shutdown_done](zx_status_t status) {
ZX_ASSERT(status == ZX_OK);
// C) Issue an explicit shutdown, check that the Vnode has
// already torn down.
//
// Note: Will not be invoked until (B) completes.
ZX_ASSERT(sync_completion_wait(vnode_destroyed, ZX_SEC(0)) == ZX_OK);
sync_completion_signal(&shutdown_done);
});
// Shutdown should be waiting for our sync to finish.
ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_MSEC(10)), ZX_ERR_TIMED_OUT);
// B) Let sync complete.
sync_completion_signal(&completions[1]);
ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_SEC(3)), ZX_OK);
vfs = nullptr;
END_TEST;
}
// Test a case where the VFS object is shut down while a clone request
// is concurrently trying to open a new connection.
bool TestTeardownSlowClone() {
BEGIN_TEST;
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
sync_completion_t completions[3];
auto vfs = std::make_unique<fs::ManagedVfs>(loop.dispatcher());
ASSERT_EQ(loop.StartThread(), ZX_OK);
auto vn = fbl::AdoptRef(new AsyncTearDownVnode(completions));
zx::channel client, server;
ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
ASSERT_EQ(vn->Open(0, nullptr), ZX_OK);
ASSERT_EQ(vn->Serve(vfs.get(), std::move(server), 0), ZX_OK);
vn = nullptr;
// A) Wait for sync to begin.
// Block the connection to the server in a sync, while simultanously
// sending a request to open a new connection.
send_sync(client);
sync_completion_wait(&completions[0], ZX_TIME_INFINITE);
zx::channel client2, server2;
ASSERT_EQ(zx::channel::create(0, &client2, &server2), ZX_OK);
ASSERT_EQ(fuchsia_io_NodeClone(client.get(), 0, server2.release()), ZX_OK);
// The connection is now:
// - In a sync callback,
// - Enqueued with a clone request,
// - Closed.
client.reset();
sync_completion_t* vnode_destroyed = &completions[2];
sync_completion_t shutdown_done;
vfs->Shutdown([&vnode_destroyed, &shutdown_done](zx_status_t status) {
ZX_ASSERT(status == ZX_OK);
// C) Issue an explicit shutdown, check that the Vnode has
// already torn down.
//
// Note: Will not be invoked until (B) completes.
ZX_ASSERT(sync_completion_wait(vnode_destroyed, ZX_SEC(0)) == ZX_OK);
sync_completion_signal(&shutdown_done);
});
// Shutdown should be waiting for our sync to finish.
ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_MSEC(10)), ZX_ERR_TIMED_OUT);
// B) Let sync complete. This should result in a successful termination
// of the filesystem, even with the pending clone request.
sync_completion_signal(&completions[1]);
ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_SEC(3)), ZX_OK);
vfs = nullptr;
END_TEST;
}
bool TestSynchronousTeardown() {
BEGIN_TEST;
async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
ASSERT_EQ(loop.StartThread(), ZX_OK);
zx::channel client;
{
// Tear down the VFS while the async loop is running.
auto vfs = std::make_unique<fs::SynchronousVfs>(loop.dispatcher());
auto vn = fbl::AdoptRef(new FdCountVnode());
zx::channel server;
ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
ASSERT_EQ(vn->Open(0, nullptr), ZX_OK);
ASSERT_EQ(vn->Serve(vfs.get(), std::move(server), 0), ZX_OK);
}
loop.Quit();
{
// Tear down the VFS while the async loop is not running.
auto vfs = std::make_unique<fs::SynchronousVfs>(loop.dispatcher());
auto vn = fbl::AdoptRef(new FdCountVnode());
zx::channel server;
ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
ASSERT_EQ(vn->Open(0, nullptr), ZX_OK);
ASSERT_EQ(vn->Serve(vfs.get(), std::move(server), 0), ZX_OK);
}
{
// Tear down the VFS with no active connections.
auto vfs = std::make_unique<fs::SynchronousVfs>(loop.dispatcher());
}
END_TEST;
}
} // namespace
BEGIN_TEST_CASE(teardown_tests)
RUN_TEST(TestUnpostedTeardown)
RUN_TEST(TestPostedTeardown)
RUN_TEST(TestTeardownDeleteThis)
RUN_TEST(TestTeardownSlowAsyncCallback)
RUN_TEST(TestTeardownSlowClone)
RUN_TEST(TestSynchronousTeardown)
END_TEST_CASE(teardown_tests)