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