1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5 //
6 #include "rocksdb/status.h"
7 #include "rocksdb/env.h"
8
9 #include <fcntl.h>
10 #include <vector>
11 #include "test_util/testharness.h"
12 #include "util/coding.h"
13 #include "util/string_util.h"
14
15 namespace ROCKSDB_NAMESPACE {
16
17 class LockTest : public testing::Test {
18 public:
19 static LockTest* current_;
20 std::string file_;
21 ROCKSDB_NAMESPACE::Env* env_;
22
LockTest()23 LockTest()
24 : file_(test::PerThreadDBPath("db_testlock_file")),
25 env_(ROCKSDB_NAMESPACE::Env::Default()) {
26 current_ = this;
27 }
28
~LockTest()29 ~LockTest() override {}
30
LockFile(FileLock ** db_lock)31 Status LockFile(FileLock** db_lock) {
32 return env_->LockFile(file_, db_lock);
33 }
34
UnlockFile(FileLock * db_lock)35 Status UnlockFile(FileLock* db_lock) {
36 return env_->UnlockFile(db_lock);
37 }
38
AssertFileIsLocked()39 bool AssertFileIsLocked(){
40 return CheckFileLock( /* lock_expected = */ true);
41 }
42
AssertFileIsNotLocked()43 bool AssertFileIsNotLocked(){
44 return CheckFileLock( /* lock_expected = */ false);
45 }
46
CheckFileLock(bool lock_expected)47 bool CheckFileLock(bool lock_expected){
48 // We need to fork to check the fcntl lock as we need
49 // to open and close the file from a different process
50 // to avoid either releasing the lock on close, or not
51 // contending for it when requesting a lock.
52
53 #ifdef OS_WIN
54
55 // WaitForSingleObject and GetExitCodeProcess can do what waitpid does.
56 // TODO - implement on Windows
57 return true;
58
59 #else
60
61 pid_t pid = fork();
62 if ( 0 == pid ) {
63 // child process
64 int exit_val = EXIT_FAILURE;
65 int fd = open(file_.c_str(), O_RDWR | O_CREAT, 0644);
66 if (fd < 0) {
67 // could not open file, could not check if it was locked
68 fprintf( stderr, "Open on on file %s failed.\n",file_.c_str());
69 exit(exit_val);
70 }
71
72 struct flock f;
73 memset(&f, 0, sizeof(f));
74 f.l_type = (F_WRLCK);
75 f.l_whence = SEEK_SET;
76 f.l_start = 0;
77 f.l_len = 0; // Lock/unlock entire file
78 int value = fcntl(fd, F_SETLK, &f);
79 if( value == -1 ){
80 if( lock_expected ){
81 exit_val = EXIT_SUCCESS;
82 }
83 } else {
84 if( ! lock_expected ){
85 exit_val = EXIT_SUCCESS;
86 }
87 }
88 close(fd); // lock is released for child process
89 exit(exit_val);
90 } else if (pid > 0) {
91 // parent process
92 int status;
93 while (-1 == waitpid(pid, &status, 0));
94 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
95 // child process exited with non success status
96 return false;
97 } else {
98 return true;
99 }
100 } else {
101 fprintf( stderr, "Fork failed\n" );
102 return false;
103 }
104 return false;
105
106 #endif
107
108 }
109
110 };
111 LockTest* LockTest::current_;
112
TEST_F(LockTest,LockBySameThread)113 TEST_F(LockTest, LockBySameThread) {
114 FileLock* lock1;
115 FileLock* lock2;
116
117 // acquire a lock on a file
118 ASSERT_OK(LockFile(&lock1));
119
120 // check the file is locked
121 ASSERT_TRUE( AssertFileIsLocked() );
122
123 // re-acquire the lock on the same file. This should fail.
124 Status s = LockFile(&lock2);
125 ASSERT_TRUE(s.IsIOError());
126 // Validate that error message contains current thread ID.
127 ASSERT_TRUE(s.ToString().find(ToString(Env::Default()->GetThreadID())) !=
128 std::string::npos);
129
130 // check the file is locked
131 ASSERT_TRUE( AssertFileIsLocked() );
132
133 // release the lock
134 ASSERT_OK(UnlockFile(lock1));
135
136 // check the file is not locked
137 ASSERT_TRUE( AssertFileIsNotLocked() );
138
139 }
140
141 } // namespace ROCKSDB_NAMESPACE
142
main(int argc,char ** argv)143 int main(int argc, char** argv) {
144 ::testing::InitGoogleTest(&argc, argv);
145 return RUN_ALL_TESTS();
146 }
147