blob: 006fa45d895d528f092cca0efaf6c3201b97e041 [file] [log] [blame] [edit]
// Copyright 2016 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 <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <random>
#include <string>
#include <vector>
#include <fbl/unique_fd.h>
#include "src/storage/fs_test/fs_test_fixture.h"
namespace fs_test {
namespace {
enum Status { kFail = -1, kBusy = 0, kDone = 1 };
constexpr int kBufSize = 65536;
static_assert(kBufSize == kBufSize / sizeof(uint64_t) * sizeof(uint64_t),
"kBufSize not multiple of uint64_t");
class Worker;
class RwWorkersTest : public FilesystemTest {
public:
RwWorkersTest();
protected:
Status DoWork();
std::vector<Worker> workers_;
std::vector<thrd_t> threads_;
};
class Worker {
public:
using WorkFn = Status (Worker::*)(void);
Worker(const std::string& where, const char* fn, WorkFn work, uint32_t size, uint32_t flags);
Status status() const { return status_; }
const std::string name() const { return name_; }
Status Work() {
status_ = (this->*work_)();
return status_;
}
Status Rw(bool do_read);
Status Writer();
private:
Status Verify();
WorkFn work_;
const std::default_random_engine::result_type seed_;
std::default_random_engine data_random_;
std::default_random_engine io_size_random_;
fbl::unique_fd fd_;
Status status_ = Status::kBusy;
const uint32_t size_;
const uint32_t flags_;
uint32_t pos_ = 0;
union {
uint8_t u8[kBufSize];
uint64_t u64[kBufSize / sizeof(uint64_t)];
} buffer_;
const std::string name_;
};
constexpr uint32_t kUseRandomIoSize = 1;
Status Worker::Rw(bool do_read) {
if (pos_ == size_) {
return kDone;
}
// offset into buffer
uint32_t off = pos_ % kBufSize;
// fill our content buffer if it's empty
if (off == 0) {
std::uniform_int_distribution<uint64_t> dist;
for (unsigned n = 0; n < (kBufSize / sizeof(uint64_t)); n++) {
buffer_.u64[n] = dist(data_random_);
}
}
// data in buffer available to write
uint32_t xfer = kBufSize - off;
// do not exceed our desired size
if (xfer > (size_ - pos_)) {
xfer = size_ - pos_;
}
if ((flags_ & kUseRandomIoSize) && (xfer > 3000)) {
xfer = 3000 + (std::uniform_int_distribution<uint32_t>()(io_size_random_) % (xfer - 3000));
}
ssize_t r;
if (do_read) {
uint8_t buffer[kBufSize];
if ((r = read(fd_.get(), buffer, xfer)) < 0) {
std::cout << "worker('" << name_ << "') read failed @" << pos_ << ": " << strerror(errno)
<< std::endl;
return kFail;
}
if (memcmp(buffer, buffer_.u8 + off, r)) {
std::cout << "worker('" << name_ << ") verify failed @" << pos_ << std::endl;
return kFail;
}
} else {
if ((r = write(fd_.get(), buffer_.u8 + off, xfer)) < 0) {
std::cout << "worker('" << name_ << "') write failed @" << pos_ << ": " << strerror(errno)
<< std::endl;
return kFail;
}
}
// advance
pos_ += r;
return kBusy;
}
Status Worker::Verify() { return Rw(true); }
Status Worker::Writer() {
Status r = Rw(false);
if (r == kDone) {
if (lseek(fd_.get(), 0, SEEK_SET) != 0) {
std::cout << "worker('" << name_ << "') seek failed: " << strerror(errno) << std::endl;
return kFail;
}
// Reset data_random_.
data_random_.seed(seed_);
pos_ = 0;
work_ = &Worker::Verify;
return kBusy;
}
return r;
}
Worker::Worker(const std::string& where, const char* fn, WorkFn work, uint32_t size, uint32_t flags)
: work_(work),
seed_(std::random_device()()),
data_random_(seed_),
io_size_random_(seed_),
size_(size),
flags_(flags),
name_(where + fn) {
fd_ = fbl::unique_fd(open(name_.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644));
EXPECT_TRUE(fd_);
}
Status RwWorkersTest::DoWork() {
int busy_count = 0;
for (Worker& w : workers_) {
if (w.status() == kBusy) {
busy_count++;
if (w.Work() == kFail) {
EXPECT_EQ(unlink(w.name().c_str()), 0);
return kFail;
}
if (w.status() == kDone) {
std::cout << "worker('" << w.name() << "') finished" << std::endl;
EXPECT_EQ(unlink(w.name().c_str()), 0);
}
}
}
return busy_count ? kBusy : kDone;
}
TEST_P(RwWorkersTest, SingleThread) {
Status r;
do {
r = DoWork();
ASSERT_NE(r, kFail);
} while (r != kDone);
}
constexpr uint32_t KiB(int n) { return n * 1024; }
constexpr uint32_t MiB(int n) { return n * 1024; }
constexpr struct {
const char* name;
uint32_t size;
uint32_t flags;
} kWork[] = {
{"file0000", KiB(512), kUseRandomIoSize},
{"file0001", MiB(10), kUseRandomIoSize},
{"file0002", KiB(512), kUseRandomIoSize},
{"file0003", KiB(512), kUseRandomIoSize},
{"file0004", KiB(512), 0},
{"file0005", MiB(20), 0},
{"file0006", KiB(512), 0},
{"file0007", KiB(512), 0},
};
RwWorkersTest::RwWorkersTest() {
// Assemble the work.
for (const auto& work : kWork) {
workers_.push_back(
Worker(fs().mount_path(), work.name, &Worker::Writer, work.size, work.flags));
}
}
int DoThreadedWork(void* arg) {
Worker* w = static_cast<Worker*>(arg);
std::cout << "work thread(" << w->name() << ") started" << std::endl;
while (w->Work() == kBusy) {
thrd_yield();
}
std::cout << "work thread(" << w->name() << ") " << (w->status() == kDone ? "finished" : "failed")
<< std::endl;
EXPECT_EQ(unlink(w->name().c_str()), 0);
return w->status();
}
TEST_P(RwWorkersTest, Concurrent) {
std::vector<thrd_t> threads;
for (Worker& w : workers_) {
// start the workers on separate threads
thrd_t t;
ASSERT_EQ(thrd_create(&t, DoThreadedWork, &w), thrd_success);
threads.push_back(t);
}
for (thrd_t t : threads) {
int rc;
ASSERT_EQ(thrd_join(t, &rc), thrd_success);
ASSERT_EQ(rc, kDone) << "Thread joined, but failed";
}
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, RwWorkersTest, testing::ValuesIn(AllTestFilesystems()),
testing::PrintToStringParamName());
} // namespace
} // namespace fs_test