shess | dd6fc7e5 | 2015-12-11 21:53:33 | [diff] [blame] | 1 | // Copyright 2015 The Chromium Authors. All rights reserved. |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "base/command_line.h" |
| 6 | #include "base/files/file.h" |
| 7 | #include "base/files/file_util.h" |
| 8 | #include "base/files/scoped_temp_dir.h" |
avi | 543540e | 2015-12-24 05:15:32 | [diff] [blame] | 9 | #include "base/macros.h" |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 10 | #include "base/test/multiprocess_test.h" |
| 11 | #include "base/test/test_timeouts.h" |
| 12 | #include "base/threading/platform_thread.h" |
| 13 | #include "base/time/time.h" |
Alexandre Elias | 52f6430 | 2017-07-26 22:23:08 | [diff] [blame] | 14 | #include "build/build_config.h" |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 15 | #include "testing/gtest/include/gtest/gtest.h" |
| 16 | #include "testing/multiprocess_func_list.h" |
| 17 | |
| 18 | using base::File; |
| 19 | using base::FilePath; |
| 20 | |
| 21 | namespace { |
| 22 | |
| 23 | // Flag for the parent to share a temp dir to the child. |
| 24 | const char kTempDirFlag[] = "temp-dir"; |
| 25 | |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 26 | // Flags to control how the process locks the file. |
| 27 | const char kFileLockShared[] = "file-lock-shared"; |
| 28 | const char kFileLockExclusive[] = "file-lock-exclusive"; |
| 29 | |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 30 | // Flags to control how the subprocess unlocks the file. |
| 31 | const char kFileUnlock[] = "file-unlock"; |
| 32 | const char kCloseUnlock[] = "close-unlock"; |
| 33 | const char kExitUnlock[] = "exit-unlock"; |
| 34 | |
| 35 | // File to lock in temp dir. |
| 36 | const char kLockFile[] = "lockfile"; |
| 37 | |
| 38 | // Constants for various requests and responses, used as |signal_file| parameter |
| 39 | // to signal/wait helpers. |
| 40 | const char kSignalLockFileLocked[] = "locked.signal"; |
| 41 | const char kSignalLockFileClose[] = "close.signal"; |
| 42 | const char kSignalLockFileClosed[] = "closed.signal"; |
| 43 | const char kSignalLockFileUnlock[] = "unlock.signal"; |
| 44 | const char kSignalLockFileUnlocked[] = "unlocked.signal"; |
| 45 | const char kSignalExit[] = "exit.signal"; |
| 46 | |
| 47 | // Signal an event by creating a file which didn't previously exist. |
| 48 | bool SignalEvent(const FilePath& signal_dir, const char* signal_file) { |
| 49 | File file(signal_dir.AppendASCII(signal_file), |
shess | dd6fc7e5 | 2015-12-11 21:53:33 | [diff] [blame] | 50 | File::FLAG_CREATE | File::FLAG_WRITE); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 51 | return file.IsValid(); |
| 52 | } |
| 53 | |
| 54 | // Check whether an event was signaled. |
| 55 | bool CheckEvent(const FilePath& signal_dir, const char* signal_file) { |
| 56 | File file(signal_dir.AppendASCII(signal_file), |
| 57 | File::FLAG_OPEN | File::FLAG_READ); |
| 58 | return file.IsValid(); |
| 59 | } |
| 60 | |
| 61 | // Busy-wait for an event to be signaled, returning false for timeout. |
| 62 | bool WaitForEventWithTimeout(const FilePath& signal_dir, |
| 63 | const char* signal_file, |
| 64 | const base::TimeDelta& timeout) { |
| 65 | const base::Time finish_by = base::Time::Now() + timeout; |
| 66 | while (!CheckEvent(signal_dir, signal_file)) { |
| 67 | if (base::Time::Now() > finish_by) |
| 68 | return false; |
| 69 | base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); |
| 70 | } |
| 71 | return true; |
| 72 | } |
| 73 | |
| 74 | // Wait forever for the event to be signaled (should never return false). |
| 75 | bool WaitForEvent(const FilePath& signal_dir, const char* signal_file) { |
| 76 | return WaitForEventWithTimeout(signal_dir, signal_file, |
| 77 | base::TimeDelta::Max()); |
| 78 | } |
| 79 | |
| 80 | // Keep these in sync so StartChild*() can refer to correct test main. |
| 81 | #define ChildMain ChildLockUnlock |
| 82 | #define ChildMainString "ChildLockUnlock" |
| 83 | |
| 84 | // Subprocess to test getting a file lock then releasing it. |kTempDirFlag| |
| 85 | // must pass in an existing temporary directory for the lockfile and signal |
| 86 | // files. One of the following flags must be passed to determine how to unlock |
| 87 | // the lock file: |
| 88 | // - |kFileUnlock| calls Unlock() to unlock. |
| 89 | // - |kCloseUnlock| calls Close() while the lock is held. |
| 90 | // - |kExitUnlock| exits while the lock is held. |
| 91 | MULTIPROCESS_TEST_MAIN(ChildMain) { |
| 92 | base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| 93 | const FilePath temp_path = command_line->GetSwitchValuePath(kTempDirFlag); |
| 94 | CHECK(base::DirectoryExists(temp_path)); |
| 95 | |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 96 | const bool use_shared_lock = command_line->HasSwitch(kFileLockShared); |
| 97 | const bool use_exclusive_lock = command_line->HasSwitch(kFileLockExclusive); |
| 98 | CHECK_NE(use_shared_lock, use_exclusive_lock); |
| 99 | |
| 100 | const File::LockMode mode = |
| 101 | use_exclusive_lock ? File::LockMode::kExclusive : File::LockMode::kShared; |
| 102 | |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 103 | // Immediately lock the file. |
| 104 | File file(temp_path.AppendASCII(kLockFile), |
| 105 | File::FLAG_OPEN | File::FLAG_READ | File::FLAG_WRITE); |
| 106 | CHECK(file.IsValid()); |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 107 | CHECK_EQ(File::FILE_OK, file.Lock(mode)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 108 | CHECK(SignalEvent(temp_path, kSignalLockFileLocked)); |
| 109 | |
| 110 | if (command_line->HasSwitch(kFileUnlock)) { |
| 111 | // Wait for signal to unlock, then unlock the file. |
| 112 | CHECK(WaitForEvent(temp_path, kSignalLockFileUnlock)); |
| 113 | CHECK_EQ(File::FILE_OK, file.Unlock()); |
| 114 | CHECK(SignalEvent(temp_path, kSignalLockFileUnlocked)); |
| 115 | } else if (command_line->HasSwitch(kCloseUnlock)) { |
| 116 | // Wait for the signal to close, then close the file. |
| 117 | CHECK(WaitForEvent(temp_path, kSignalLockFileClose)); |
| 118 | file.Close(); |
| 119 | CHECK(!file.IsValid()); |
| 120 | CHECK(SignalEvent(temp_path, kSignalLockFileClosed)); |
| 121 | } else { |
| 122 | CHECK(command_line->HasSwitch(kExitUnlock)); |
| 123 | } |
| 124 | |
| 125 | // Wait for signal to exit, so that unlock or close can be distinguished from |
| 126 | // exit. |
| 127 | CHECK(WaitForEvent(temp_path, kSignalExit)); |
| 128 | return 0; |
| 129 | } |
| 130 | |
| 131 | } // namespace |
| 132 | |
| 133 | class FileLockingTest : public testing::Test { |
| 134 | public: |
Chris Watkins | bb7211c | 2017-11-29 07:16:38 | [diff] [blame] | 135 | FileLockingTest() = default; |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 136 | |
| 137 | protected: |
| 138 | void SetUp() override { |
| 139 | testing::Test::SetUp(); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 140 | |
shess | dd6fc7e5 | 2015-12-11 21:53:33 | [diff] [blame] | 141 | // Setup the temp dir and the lock file. |
| 142 | ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 143 | lock_file_.Initialize( |
vabr | 411f4fc | 2016-09-08 09:26:27 | [diff] [blame] | 144 | temp_dir_.GetPath().AppendASCII(kLockFile), |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 145 | File::FLAG_CREATE | File::FLAG_READ | File::FLAG_WRITE); |
| 146 | ASSERT_TRUE(lock_file_.IsValid()); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 147 | } |
| 148 | |
| 149 | bool SignalEvent(const char* signal_file) { |
vabr | 411f4fc | 2016-09-08 09:26:27 | [diff] [blame] | 150 | return ::SignalEvent(temp_dir_.GetPath(), signal_file); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 151 | } |
| 152 | |
| 153 | bool WaitForEventOrTimeout(const char* signal_file) { |
vabr | 411f4fc | 2016-09-08 09:26:27 | [diff] [blame] | 154 | return ::WaitForEventWithTimeout(temp_dir_.GetPath(), signal_file, |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 155 | TestTimeouts::action_timeout()); |
| 156 | } |
| 157 | |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 158 | // Start a child process set to use the specified locking mode and unlock |
| 159 | // action, and wait for it to lock the file. |
| 160 | void StartChildAndSignalLock(File::LockMode lock_mode, |
| 161 | const char* unlock_action) { |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 162 | // Create a temporary dir and spin up a ChildLockExit subprocess against it. |
vabr | 411f4fc | 2016-09-08 09:26:27 | [diff] [blame] | 163 | const FilePath temp_path = temp_dir_.GetPath(); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 164 | base::CommandLine child_command_line( |
| 165 | base::GetMultiProcessTestChildBaseCommandLine()); |
| 166 | child_command_line.AppendSwitchPath(kTempDirFlag, temp_path); |
| 167 | child_command_line.AppendSwitch(unlock_action); |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 168 | switch (lock_mode) { |
| 169 | case File::LockMode::kExclusive: |
| 170 | child_command_line.AppendSwitch(kFileLockExclusive); |
| 171 | break; |
| 172 | case File::LockMode::kShared: |
| 173 | child_command_line.AppendSwitch(kFileLockShared); |
| 174 | break; |
| 175 | } |
Jay Civelli | 4a44260b | 2017-08-21 19:26:29 | [diff] [blame] | 176 | lock_child_ = base::SpawnMultiProcessTestChild( |
jcivelli | 87c322b | 2017-03-14 17:55:53 | [diff] [blame] | 177 | ChildMainString, child_command_line, base::LaunchOptions()); |
Jay Civelli | 4a44260b | 2017-08-21 19:26:29 | [diff] [blame] | 178 | ASSERT_TRUE(lock_child_.IsValid()); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 179 | |
| 180 | // Wait for the child to lock the file. |
| 181 | ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileLocked)); |
| 182 | } |
| 183 | |
| 184 | // Signal the child to exit cleanly. |
| 185 | void ExitChildCleanly() { |
| 186 | ASSERT_TRUE(SignalEvent(kSignalExit)); |
| 187 | int rv = -1; |
jcivelli | f4462a35 | 2017-01-10 04:45:59 | [diff] [blame] | 188 | ASSERT_TRUE(WaitForMultiprocessTestChildExit( |
Jay Civelli | 4a44260b | 2017-08-21 19:26:29 | [diff] [blame] | 189 | lock_child_, TestTimeouts::action_timeout(), &rv)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 190 | ASSERT_EQ(0, rv); |
| 191 | } |
| 192 | |
| 193 | base::ScopedTempDir temp_dir_; |
| 194 | base::File lock_file_; |
Jay Civelli | 4a44260b | 2017-08-21 19:26:29 | [diff] [blame] | 195 | base::Process lock_child_; |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 196 | |
| 197 | private: |
| 198 | DISALLOW_COPY_AND_ASSIGN(FileLockingTest); |
| 199 | }; |
| 200 | |
| 201 | // Test that locks are released by Unlock(). |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 202 | TEST_F(FileLockingTest, LockAndUnlockExclusive) { |
| 203 | StartChildAndSignalLock(File::LockMode::kExclusive, kFileUnlock); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 204 | |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 205 | ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
| 206 | ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 207 | ASSERT_TRUE(SignalEvent(kSignalLockFileUnlock)); |
| 208 | ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileUnlocked)); |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 209 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
| 210 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 211 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
| 212 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 213 | |
| 214 | ExitChildCleanly(); |
| 215 | } |
| 216 | TEST_F(FileLockingTest, LockAndUnlockShared) { |
| 217 | StartChildAndSignalLock(File::LockMode::kShared, kFileUnlock); |
| 218 | |
| 219 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
| 220 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 221 | ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
| 222 | ASSERT_TRUE(SignalEvent(kSignalLockFileUnlock)); |
| 223 | ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileUnlocked)); |
| 224 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
| 225 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 226 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 227 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 228 | |
| 229 | ExitChildCleanly(); |
| 230 | } |
| 231 | |
| 232 | // Test that locks are released on Close(). |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 233 | TEST_F(FileLockingTest, UnlockOnCloseExclusive) { |
| 234 | StartChildAndSignalLock(File::LockMode::kExclusive, kCloseUnlock); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 235 | |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 236 | ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
| 237 | ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 238 | ASSERT_TRUE(SignalEvent(kSignalLockFileClose)); |
| 239 | ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileClosed)); |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 240 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
| 241 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 242 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
| 243 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 244 | |
| 245 | ExitChildCleanly(); |
| 246 | } |
| 247 | TEST_F(FileLockingTest, UnlockOnCloseShared) { |
| 248 | StartChildAndSignalLock(File::LockMode::kShared, kCloseUnlock); |
| 249 | |
| 250 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
| 251 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 252 | ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
| 253 | ASSERT_TRUE(SignalEvent(kSignalLockFileClose)); |
| 254 | ASSERT_TRUE(WaitForEventOrTimeout(kSignalLockFileClosed)); |
| 255 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
| 256 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 257 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 258 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 259 | |
| 260 | ExitChildCleanly(); |
| 261 | } |
| 262 | |
| 263 | // Test that locks are released on exit. |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 264 | TEST_F(FileLockingTest, UnlockOnExitExclusive) { |
| 265 | StartChildAndSignalLock(File::LockMode::kExclusive, kExitUnlock); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 266 | |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 267 | ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
| 268 | ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 269 | ExitChildCleanly(); |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 270 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
| 271 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 272 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
| 273 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 274 | } |
| 275 | TEST_F(FileLockingTest, UnlockOnExitShared) { |
| 276 | StartChildAndSignalLock(File::LockMode::kShared, kExitUnlock); |
| 277 | |
| 278 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
| 279 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 280 | ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
| 281 | ExitChildCleanly(); |
| 282 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
| 283 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 284 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 285 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 286 | } |
| 287 | |
| 288 | // Test that killing the process releases the lock. This should cover crashing. |
Jeremy Roman | 1a2e29b2 | 2017-08-16 18:20:00 | [diff] [blame] | 289 | // Flaky on Android (http://crbug.com/747518) |
| 290 | #if defined(OS_ANDROID) |
| 291 | #define MAYBE_UnlockOnTerminate DISABLED_UnlockOnTerminate |
| 292 | #else |
| 293 | #define MAYBE_UnlockOnTerminate UnlockOnTerminate |
| 294 | #endif |
| 295 | TEST_F(FileLockingTest, MAYBE_UnlockOnTerminate) { |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 296 | // The child will wait for an exit which never arrives. |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 297 | StartChildAndSignalLock(File::LockMode::kExclusive, kExitUnlock); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 298 | |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 299 | ASSERT_NE(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
Jay Civelli | 4a44260b | 2017-08-21 19:26:29 | [diff] [blame] | 300 | ASSERT_TRUE(TerminateMultiProcessTestChild(lock_child_, 0, true)); |
Victor Costan | 1cf421f | 2019-01-28 19:35:59 | [diff] [blame] | 301 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kShared)); |
| 302 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 303 | ASSERT_EQ(File::FILE_OK, lock_file_.Lock(File::LockMode::kExclusive)); |
shess | e66da3f | 2015-12-10 22:12:17 | [diff] [blame] | 304 | ASSERT_EQ(File::FILE_OK, lock_file_.Unlock()); |
| 305 | } |