loop: add clear_fd_wait method

Add a helper method that clears the fd for a given loop device but
also ensures that the loop device is not bound to the supplied fd
anymore. Check the function documentation for more information.
Add a corresponding test.
This commit is contained in:
Christian Kellner 2021-08-08 13:32:25 +00:00 committed by Tom Gundersen
parent a367a0df1d
commit d8e48c0511
2 changed files with 127 additions and 0 deletions

View file

@ -4,6 +4,8 @@
import contextlib
import os
import time
import threading
from tempfile import TemporaryDirectory, TemporaryFile
import pytest
@ -87,3 +89,72 @@ def test_basic(tempdir):
with pytest.raises(RuntimeError):
ctl.loop_for_fd(0)
@pytest.mark.skipif(not TestBase.can_bind_mount(), reason="root only")
def test_clear_fd_wait(tempdir):
path = os.path.join(tempdir, "test.img")
ctl = loop.LoopControl()
assert ctl
delay_time = 0.25
def close_loop(lo, barrier):
barrier.wait()
time.sleep(delay_time)
print("closing loop")
lo.close()
lo, lo2, f = None, None, None
try:
f = open(path, "wb+")
f.truncate(1024)
f.flush()
lo = ctl.loop_for_fd(f.fileno(), autoclear=False)
assert lo
# Increase reference count of the loop to > 1 thus
# preventing the kernel from immediately closing the
# device. Instead the kernel will set the autoclear
# attribute and return
lo2 = loop.Loop(lo.minor)
assert lo2
# as long as the second loop is alive, the kernel can
# not clear the fd and thus we will get a timeout
with pytest.raises(TimeoutError):
lo.clear_fd_wait(f.fileno(), 0.1, 0.01)
# start a thread and sync with a barrier, then close
# the loop device in the background thread while the
# main thread is waiting in `clear_fd_wait`. We wait
# four times the delay time of the thread to ensure
# we don't get a timeout.
barrier = threading.Barrier(2)
thread = threading.Thread(
target=close_loop,
args=(lo2, barrier)
)
barrier.reset()
thread.start()
barrier.wait()
lo.clear_fd_wait(f.fileno(), 4*delay_time, delay_time/10)
# no timeout exception has occurred and thus the device
# must not be be bound to the original file anymore
assert not lo.is_bound_to(f.fileno())
finally:
if lo2:
lo2.close()
if lo:
with contextlib.suppress(OSError):
lo.clear_fd()
lo.close()
if f:
f.close()
ctl.close()