blob: cf2577606107a1f64bf156abecbdb45f7805787b [file] [log] [blame]
// Copyright 2020 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 <fuchsia/io/llcpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <stdio.h>
#include <zircon/fidl.h>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <utility>
#include <vector>
#include <fs/pseudo_dir.h>
#include <fs/pseudo_file.h>
#include <fs/synchronous_vfs.h>
#include <zxtest/zxtest.h>
namespace {
namespace fio = ::llcpp::fuchsia::io;
// Vnode that gives us control of when it replies to messages
class TestVnode : public fs::Vnode {
public:
TestVnode() = default;
fs::VnodeProtocolSet GetProtocols() const override { return fs::VnodeProtocol::kFile; }
zx_status_t GetNodeInfoForProtocol(fs::VnodeProtocol protocol, fs::Rights,
fs::VnodeRepresentation* info) override {
ZX_ASSERT(protocol == fs::VnodeProtocol::kFile);
*info = fs::VnodeRepresentation::File();
return ZX_OK;
}
// The test code below sends a message unrecognized by fs::Vnode, and we use that to make this
// transaction asynchronous and enqueue the transaction to be completed when desired by the test
// logic.
void HandleFsSpecificMessage(fidl_msg_t* msg, fidl::Transaction* txn) override {
std::unique_lock<std::mutex> guard(transactions_lock_);
transactions_.push_back(txn->TakeOwnership());
transactions_cv_.notify_all();
}
// Blocks until the the FIDL message has been dispatched to HandleFsSpecificMessage, and the
// transaction is available
std::unique_ptr<fidl::Transaction> GetNextInflightTransaction() {
std::unique_lock<std::mutex> guard(transactions_lock_);
transactions_cv_.wait(guard, [this]() { return !transactions_.empty(); });
auto result = std::move(transactions_.back());
transactions_.pop_back();
return result;
}
private:
std::mutex transactions_lock_;
std::condition_variable transactions_cv_;
std::vector<std::unique_ptr<fidl::Transaction>> transactions_;
};
class TransactionCountingTest : public zxtest::Test {
public:
// Setup file structure with one directory and one file. Note: On creation
// directories and files have no flags and rights.
TransactionCountingTest() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {
vfs_.SetDispatcher(loop_.dispatcher());
root_ = fbl::AdoptRef<fs::PseudoDir>(new fs::PseudoDir());
file_ = fbl::AdoptRef<TestVnode>(new TestVnode());
root_->AddEntry("file", file_);
}
zx_status_t ConnectClient(zx::channel server_end) {
// Serve root directory with maximum rights
return vfs_.ServeDirectory(root_, std::move(server_end));
}
std::unique_ptr<fidl::Transaction> GetNextInflightTransaction() {
return file_->GetNextInflightTransaction();
}
size_t inflight_transactions() { return file_->inflight_transactions(); }
protected:
void SetUp() override { loop_.StartThread(); }
void TearDown() override { loop_.Shutdown(); }
private:
async::Loop loop_;
fs::SynchronousVfs vfs_;
fbl::RefPtr<fs::PseudoDir> root_;
fbl::RefPtr<TestVnode> file_;
};
void SendHangingMessage(const zx::channel& c) {
fidl_message_header_t hdr = {};
fidl_init_txn_header(&hdr, 1, 1);
ASSERT_OK(c.write(0, &hdr, sizeof(hdr), nullptr, 0));
}
TEST_F(TransactionCountingTest, CountStartsAtZero) {
// Create connection to vfs
zx::channel client_end, server_end;
ASSERT_OK(zx::channel::create(0u, &client_end, &server_end));
ASSERT_OK(ConnectClient(std::move(server_end)));
ASSERT_EQ(inflight_transactions(), 0);
// Connect to file
zx::channel fc1, fc1_remote;
ASSERT_OK(zx::channel::create(0u, &fc1, &fc1_remote));
ASSERT_OK(fdio_open_at(client_end.get(), "file", fio::OPEN_RIGHT_READABLE, fc1_remote.release()));
ASSERT_EQ(inflight_transactions(), 0);
}
TEST_F(TransactionCountingTest, SingleTransactionInflightReplyShortMessage) {
// Create connection to vfs
zx::channel client_end, server_end;
ASSERT_OK(zx::channel::create(0u, &client_end, &server_end));
ASSERT_OK(ConnectClient(std::move(server_end)));
// Connect to file
zx::channel fc1, fc1_remote;
ASSERT_OK(zx::channel::create(0u, &fc1, &fc1_remote));
ASSERT_OK(fdio_open_at(client_end.get(), "file", fio::OPEN_RIGHT_READABLE, fc1_remote.release()));
SendHangingMessage(fc1);
{
auto txn = GetNextInflightTransaction();
ASSERT_EQ(inflight_transactions(), 1);
txn->Reply({});
// Count drops when the transaction object is destroyed
ASSERT_EQ(inflight_transactions(), 1);
}
ASSERT_EQ(inflight_transactions(), 0);
}
TEST_F(TransactionCountingTest, SingleTransactionInflightReplyValidMessage) {
// Create connection to vfs
zx::channel client_end, server_end;
ASSERT_OK(zx::channel::create(0u, &client_end, &server_end));
ASSERT_OK(ConnectClient(std::move(server_end)));
// Connect to file
zx::channel fc1, fc1_remote;
ASSERT_OK(zx::channel::create(0u, &fc1, &fc1_remote));
ASSERT_OK(fdio_open_at(client_end.get(), "file", fio::OPEN_RIGHT_READABLE, fc1_remote.release()));
SendHangingMessage(fc1);
{
auto txn = GetNextInflightTransaction();
ASSERT_EQ(inflight_transactions(), 1);
fidl_message_header_t hdr = {};
fidl_init_txn_header(&hdr, 1, 1);
txn->Reply(fidl::Message(
fidl::BytePart(reinterpret_cast<uint8_t*>(&hdr), sizeof(hdr), sizeof(hdr)), {}));
// Count drops when the transaction object is destroyed
ASSERT_EQ(inflight_transactions(), 1);
}
ASSERT_EQ(inflight_transactions(), 0);
}
TEST_F(TransactionCountingTest, SingleTransactionInflightCloseOnMessage) {
// Create connection to vfs
zx::channel client_end, server_end;
ASSERT_OK(zx::channel::create(0u, &client_end, &server_end));
ASSERT_OK(ConnectClient(std::move(server_end)));
// Connect to file
zx::channel fc1, fc1_remote;
ASSERT_OK(zx::channel::create(0u, &fc1, &fc1_remote));
ASSERT_OK(fdio_open_at(client_end.get(), "file", fio::OPEN_RIGHT_READABLE, fc1_remote.release()));
SendHangingMessage(fc1);
{
auto txn = GetNextInflightTransaction();
ASSERT_EQ(inflight_transactions(), 1);
txn->Close(ZX_OK);
// Count drops when the transaction object is destroyed
ASSERT_EQ(inflight_transactions(), 1);
}
ASSERT_EQ(inflight_transactions(), 0);
}
TEST_F(TransactionCountingTest, MultipleTransactionsInflight) {
// Create connection to vfs
zx::channel client_end, server_end;
ASSERT_OK(zx::channel::create(0u, &client_end, &server_end));
ASSERT_OK(ConnectClient(std::move(server_end)));
// Connect to file twice
zx::channel fc1, fc1_remote;
ASSERT_OK(zx::channel::create(0u, &fc1, &fc1_remote));
ASSERT_OK(fdio_open_at(client_end.get(), "file", fio::OPEN_RIGHT_READABLE, fc1_remote.release()));
zx::channel fc2, fc2_remote;
ASSERT_OK(zx::channel::create(0u, &fc2, &fc2_remote));
ASSERT_OK(fdio_open_at(client_end.get(), "file", fio::OPEN_RIGHT_READABLE, fc2_remote.release()));
SendHangingMessage(fc1);
auto txn1 = GetNextInflightTransaction();
SendHangingMessage(fc2);
auto txn2 = GetNextInflightTransaction();
ASSERT_EQ(inflight_transactions(), 2);
txn1->Reply({});
txn1.reset();
ASSERT_EQ(inflight_transactions(), 1);
txn2->Reply({});
txn2.reset();
ASSERT_EQ(inflight_transactions(), 0);
}
} // namespace