Skip to content

Commit 59301ce

Browse files
Chandhana Solainathanmeta-codesync[bot]
authored andcommitted
Add integration tests for busy bind mount handling
Summary: Verifies eden rm behavior when a bind mount is actively in use. Reviewed By: vilatto Differential Revision: D92106564 fbshipit-source-id: 4728cacafeb89bca9f6db31c7761ccd6df075193
1 parent 5051a4d commit 59301ce

1 file changed

Lines changed: 94 additions & 0 deletions

File tree

eden/integration/remove_test.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import json
1010
import os
11+
import subprocess
12+
import sys
1113
import time
1214
from typing import Set
1315

@@ -181,3 +183,95 @@ def test_remove_aux_process_timeout(self, impl: str, env: dict) -> None:
181183
os.path.exists(mount2),
182184
"second mount point should be removed",
183185
)
186+
187+
@parameterized.expand(
188+
[
189+
("rust", {"EDENFSCTL_ONLY_RUST": "1"}),
190+
("python", {"EDENFSCTL_SKIP_RUST": "1"}),
191+
]
192+
)
193+
def test_remove_with_busy_bind_mount(self, impl: str, env: dict) -> None:
194+
"""Test eden rm behavior when a bind mount is actively in use.
195+
196+
Creates a bind redirection and holds an open file handle on a file in
197+
the bind mount. This simulates an aux process (like buck) actively
198+
using the redirection.
199+
200+
This test only runs on macOS because unmount behavior differs by platform:
201+
- macOS: uses MNT_FORCE for unmount, which can hang if the mount is busy
202+
- Linux: uses MNT_DETACH (lazy unmount), always succeeds immediately
203+
- Windows: uses symlinks instead of bind mounts, unlink() is instant
204+
205+
The test verifies that eden rm with --timeout completes without hanging
206+
indefinitely, even when the bind mount is in active use.
207+
"""
208+
if sys.platform != "darwin":
209+
self.skipTest(
210+
"Busy bind mount test is macOS-only (Linux/Windows unmounts don't hang)"
211+
)
212+
213+
# Setup: add a bind redirection
214+
repo_path = f"busy-{impl}"
215+
self.eden.run_cmd("redirect", "add", "--mount", self.mount, repo_path, "bind")
216+
217+
# Get the mount point path (inside the checkout)
218+
mount_point = os.path.join(self.mount, repo_path)
219+
self.assertTrue(
220+
os.path.isdir(mount_point), f"Mount point should exist: {mount_point}"
221+
)
222+
223+
# Keep the mount busy by holding an open file handle.
224+
test_file = os.path.join(mount_point, "busy_file")
225+
with open(test_file, "w") as f:
226+
f.write("x")
227+
busy_proc = subprocess.Popen(
228+
["tail", "-f", test_file],
229+
stdout=subprocess.DEVNULL,
230+
stderr=subprocess.DEVNULL,
231+
)
232+
233+
try:
234+
# Give the process a moment to start and open the file
235+
time.sleep(0.5)
236+
237+
# Verify the process is running and has the file open
238+
self.assertIsNone(busy_proc.poll(), "busy process should still be running")
239+
240+
# Run eden rm with a timeout
241+
start_time = time.time()
242+
result = self.eden.run_unchecked(
243+
"remove",
244+
"--yes",
245+
"--timeout",
246+
"1",
247+
self.mount,
248+
env=env,
249+
capture_output=True,
250+
text=True,
251+
)
252+
elapsed_time = time.time() - start_time
253+
254+
# The operation should complete (not hang indefinitely)
255+
# On macOS, MNT_FORCE could hang on busy mounts, but --timeout should prevent that (timeout + overhead = ~2s)
256+
self.assertLess(
257+
elapsed_time,
258+
2.0,
259+
f"eden rm ({impl}) should not hang indefinitely with busy bind mount",
260+
)
261+
262+
# Log the output for debugging
263+
output = result.stderr or ""
264+
265+
# Verify the mount was removed despite the busy bind mount
266+
self.assertFalse(
267+
os.path.exists(self.mount),
268+
f"mount point should be removed after eden rm ({impl}) with busy bind mount. Output: {output}",
269+
)
270+
finally:
271+
# Clean up the busy process
272+
busy_proc.terminate()
273+
try:
274+
busy_proc.wait(timeout=5)
275+
except subprocess.TimeoutExpired:
276+
busy_proc.kill()
277+
busy_proc.wait()

0 commit comments

Comments
 (0)