blob: 2db7795e550f3f640fb8077848cfdc5c4237412b [file] [log] [blame]
// Copyright 2021 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 <lib/file-lock/file-lock.h>
#include <zxtest/zxtest.h>
namespace file_lock {
class CallbackState {
public:
CallbackState() : called_(false), error_(false), status_(ZX_ERR_INTERNAL) {}
bool status_valid() const { return called_ && !error_; }
zx_status_t status() const { return status_; }
void Callback(zx_status_t status) {
if (called_) {
error_ = true;
return;
}
called_ = true;
status_ = status;
}
void reset() {
called_ = false;
status_ = false;
}
private:
bool called_;
bool error_;
zx_status_t status_;
};
struct DummyLockClient {
DummyLockClient(FileLock& shared_lock, zx_koid_t client_koid)
: lock(shared_lock), koid(client_koid) {
reset();
}
~DummyLockClient() { lock.Forget(koid); }
FileLock& lock;
zx_koid_t koid;
CallbackState state;
lock_completer_t completer;
void reset() {
completer = lock_completer_t([this](zx_status_t status) { this->state.Callback(status); });
state.reset();
}
void DoLock(LockType type, bool wait = true) {
LockRequest req(type, wait);
reset();
lock.Lock(koid, req, completer);
}
};
#define ASSERT_BLOCK(client) ASSERT_FALSE((client).state.status_valid())
#define ASSERT_RETURN(client, call_status) \
ASSERT_TRUE((client).state.status_valid() && (call_status) == (client).state.status())
TEST(FileLock, CallbackTest) {
FileLock lock;
CallbackState result;
EXPECT_FALSE(result.status_valid());
result.Callback(ZX_ERR_SHOULD_WAIT);
EXPECT_TRUE(result.status_valid());
EXPECT_EQ(ZX_ERR_SHOULD_WAIT, result.status());
result.Callback(ZX_OK); // Callback called twice
EXPECT_FALSE(result.status_valid());
}
TEST(FileLock, LockUnlock) {
FileLock lock;
DummyLockClient client1(lock, 1);
client1.DoLock(LockType::READ);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::READ);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::READ);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
}
TEST(FileLock, TwoReads) {
FileLock lock;
DummyLockClient client1(lock, 1);
DummyLockClient client2(lock, 2);
client1.DoLock(LockType::READ);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::READ);
ASSERT_RETURN(client2, ZX_OK);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client2, ZX_OK);
}
TEST(FileLock, WriteBlocksReads) {
FileLock lock;
DummyLockClient client1(lock, 1);
DummyLockClient client2(lock, 2);
DummyLockClient client3(lock, 3);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::READ);
ASSERT_BLOCK(client2);
client3.DoLock(LockType::READ);
ASSERT_BLOCK(client3);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
// client2 and client 3 should be unblocked
ASSERT_RETURN(client2, ZX_OK);
ASSERT_RETURN(client3, ZX_OK);
client2.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client2, ZX_OK);
client3.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client3, ZX_OK);
}
TEST(FileLock, ReadsBlockWrite) {
FileLock lock;
DummyLockClient client1(lock, 1);
DummyLockClient client2(lock, 2);
DummyLockClient client3(lock, 3);
client1.DoLock(LockType::READ);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::READ);
ASSERT_RETURN(client2, ZX_OK);
client3.DoLock(LockType::WRITE);
ASSERT_BLOCK(client3);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
ASSERT_BLOCK(client3); // write lock still blocked
client2.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
ASSERT_RETURN(client3, ZX_OK); // write lock attained
client3.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client3, ZX_OK);
}
TEST(FileLock, LockLeftHanging) {
FileLock lock;
{
DummyLockClient client1(lock, 1);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
}
{
DummyLockClient client2(lock, 1);
client2.DoLock(LockType::WRITE);
ASSERT_RETURN(client2, ZX_OK);
}
}
TEST(FileLock, ReadNoBlockWrite) {
FileLock lock;
DummyLockClient client1(lock, 1);
DummyLockClient client2(lock, 2);
client1.DoLock(LockType::READ);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::WRITE, false);
ASSERT_RETURN(client2, ZX_ERR_SHOULD_WAIT);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
}
TEST(FileLock, WriteNoBlockRead) {
FileLock lock;
DummyLockClient client1(lock, 1);
DummyLockClient client2(lock, 2);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::READ, false);
ASSERT_RETURN(client2, ZX_ERR_SHOULD_WAIT);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
}
TEST(FileLock, WriteToRead) {
FileLock lock;
DummyLockClient client1(lock, 1);
DummyLockClient client2(lock, 2);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::READ);
ASSERT_BLOCK(client2);
client1.DoLock(LockType::READ); // also unblock client2
ASSERT_RETURN(client1, ZX_OK);
ASSERT_RETURN(client2, ZX_OK);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client2, ZX_OK);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::READ);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
}
TEST(FileLock, ReadToWrite) {
FileLock lock;
DummyLockClient client1(lock, 1);
DummyLockClient client2(lock, 2);
client1.DoLock(LockType::READ);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::READ);
ASSERT_RETURN(client2, ZX_OK);
client1.DoLock(LockType::WRITE, false);
ASSERT_RETURN(client1, ZX_ERR_SHOULD_WAIT);
client1.DoLock(LockType::WRITE);
ASSERT_BLOCK(client1);
client2.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client2, ZX_OK);
ASSERT_RETURN(client1, ZX_OK); // client1 now has write lock
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::READ);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
}
TEST(FileLock, OneWriteBeatsOneRead) {
FileLock lock;
DummyLockClient client1(lock, 1);
DummyLockClient client2(lock, 2);
DummyLockClient client3(lock, 3);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::WRITE);
ASSERT_BLOCK(client2);
client3.DoLock(LockType::READ);
ASSERT_BLOCK(client3);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
ASSERT_RETURN(client2, ZX_OK);
ASSERT_BLOCK(client3);
client2.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client2, ZX_OK);
ASSERT_RETURN(client3, ZX_OK);
client3.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client3, ZX_OK);
}
TEST(FileLock, TwoReadsBeatOneWrite) {
FileLock lock;
DummyLockClient client1(lock, 1);
DummyLockClient client2(lock, 2);
DummyLockClient client3(lock, 3);
DummyLockClient client4(lock, 4);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::WRITE);
ASSERT_BLOCK(client2);
client3.DoLock(LockType::READ);
ASSERT_BLOCK(client3);
client4.DoLock(LockType::READ);
ASSERT_BLOCK(client4);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client3, ZX_OK);
ASSERT_RETURN(client4, ZX_OK);
ASSERT_BLOCK(client2);
client3.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client3, ZX_OK);
ASSERT_BLOCK(client2);
client4.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client4, ZX_OK);
ASSERT_RETURN(client2, ZX_OK);
client2.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client2, ZX_OK);
}
TEST(FileLock, UnlockWhileBlocked) {
FileLock lock;
DummyLockClient client1(lock, 1);
DummyLockClient client2(lock, 2);
client1.DoLock(LockType::WRITE);
ASSERT_RETURN(client1, ZX_OK);
client2.DoLock(LockType::WRITE);
ASSERT_BLOCK(client2);
client2.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client2, ZX_ERR_BAD_STATE);
client1.DoLock(LockType::UNLOCK);
ASSERT_RETURN(client1, ZX_OK);
}
} // namespace file_lock