mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
102 lines
3.9 KiB
Python
102 lines
3.9 KiB
Python
"""Real cross-process contention at the late stack publication boundary."""
|
|
import contextlib
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'remote'))
|
|
import oci_instances as instances
|
|
|
|
HOLDER = '''import fcntl,sys,time
|
|
with open(sys.argv[1], 'a') as lock:
|
|
fcntl.flock(lock, fcntl.LOCK_EX)
|
|
print('locked', flush=True)
|
|
sys.stdin.readline()
|
|
'''
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def competing_owner(root):
|
|
process = subprocess.Popen([sys.executable, '-u', '-c', HOLDER, str(root / '.lock')],
|
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
|
|
try:
|
|
if process.stdout.readline().strip() != 'locked':
|
|
raise RuntimeError('Lock fixture did not acquire the lock')
|
|
yield process
|
|
finally:
|
|
if process.poll() is None:
|
|
process.terminate()
|
|
process.wait(timeout=5)
|
|
process.stdout.close()
|
|
process.stdin.close()
|
|
|
|
|
|
class InstanceLockWaitTests(unittest.TestCase):
|
|
def test_waiting_publication_succeeds_after_another_process_releases(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
with competing_owner(root) as process:
|
|
def release():
|
|
process.stdin.write('release\n')
|
|
process.stdin.flush()
|
|
timer = threading.Timer(0.35, release)
|
|
start = time.monotonic()
|
|
timer.start()
|
|
try:
|
|
with instances.locked(root, timeout=2):
|
|
elapsed = time.monotonic() - start
|
|
self.assertGreaterEqual(elapsed, 0.2)
|
|
self.assertLess(elapsed, 2)
|
|
finally:
|
|
timer.cancel()
|
|
timer.join()
|
|
|
|
def test_default_remains_immediate_when_another_operation_owns_the_registry(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
with competing_owner(root):
|
|
start = time.monotonic()
|
|
with self.assertRaises(BlockingIOError), instances.locked(root):
|
|
self.fail('Contended default lock was acquired')
|
|
self.assertLess(time.monotonic() - start, 0.5)
|
|
|
|
def test_wait_has_a_finite_timeout(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
with competing_owner(root):
|
|
start = time.monotonic()
|
|
with self.assertRaises(BlockingIOError), instances.locked(root, timeout=0.1):
|
|
self.fail('Contended timed lock was acquired')
|
|
elapsed = time.monotonic() - start
|
|
self.assertGreaterEqual(elapsed, 0.08)
|
|
self.assertLess(elapsed, 0.6)
|
|
|
|
def test_error_inside_operation_is_propagated_without_repeating_it(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
entered = []
|
|
with self.assertRaises(BlockingIOError):
|
|
with instances.locked(root, timeout=2):
|
|
entered.append(True)
|
|
raise BlockingIOError('unrelated operation failure')
|
|
self.assertEqual(entered, [True])
|
|
with instances.locked(root):
|
|
pass
|
|
|
|
def test_permission_failure_is_not_retried(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
with patch.object(instances.fcntl, 'flock', side_effect=PermissionError('denied')), \
|
|
patch.object(instances.time, 'sleep') as sleep:
|
|
with self.assertRaises(PermissionError), instances.locked(Path(directory), timeout=2):
|
|
pass
|
|
sleep.assert_not_called()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|