| // 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/vnode.h> |
| #include <fs/vfs.h> |
| #include <fuchsia/io/c/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/zx/channel.h> |
| #include <lib/sync/completion.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; |
| } |
| |
| 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 = fbl::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 = fbl::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 = fbl::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 = fbl::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 = fbl::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) |