| // 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 |