#!/bin/bash set -e RESULT_DIR=/data RESULT_FILE="$RESULT_DIR/space-lock-test-$(date -u +%Y%m%dT%H%M%SZ).txt" mkdir -p "$RESULT_DIR" # Dummy HTTP server so the Space stays running and serves the result python3 -c " import http.server, socketserver class H(http.server.BaseHTTPRequestHandler): def do_GET(self): self.send_response(200); self.send_header('Content-Type','text/plain'); self.end_headers() try: with open('$RESULT_FILE') as f: self.wfile.write(f.read().encode()) except FileNotFoundError: self.wfile.write(b'running...') def log_message(self,*a,**k): pass socketserver.TCPServer.allow_reuse_address = True with socketserver.TCPServer(('0.0.0.0',7860), H) as s: s.serve_forever() " & SERVER_PID=$! ( echo "=== Space bucket-mount lock repro ===" echo "date: $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "hostname: $(hostname)" echo "kernel: $(uname -r)" echo "sqlite: $(python3 -c 'import sqlite3;print(sqlite3.sqlite_version)')" echo echo "=== mount /data ===" mount | grep -E "data|fuse" || echo "(no fuse mount)" echo echo "=== fcntl POSIX inter-process lock ===" cat > /tmp/fcntl_test.py << 'PYEOF' import fcntl, os, sys, time, struct path, role = sys.argv[1], sys.argv[2] fd = os.open(path, os.O_RDWR | os.O_CREAT, 0o644) ld = struct.pack("hhqqi", fcntl.F_WRLCK, os.SEEK_SET, 0, 1, 0) if role == "holder": fcntl.fcntl(fd, fcntl.F_SETLK, ld); print("HOLDER: acquired"); time.sleep(3); print("HOLDER: done") else: time.sleep(0.5) try: fcntl.fcntl(fd, fcntl.F_SETLK, ld); print("CONTENDER: got it (BAD)"); sys.exit(2) except BlockingIOError: print("CONTENDER: EAGAIN (expected)") PYEOF rm -f /data/locktest.bin python3 /tmp/fcntl_test.py /data/locktest.bin holder > /tmp/h.out 2>&1 & HPID=$! sleep 0.1 python3 /tmp/fcntl_test.py /data/locktest.bin contender; CR=$? wait $HPID; HR=$? echo "HOLDER (exit=$HR):"; cat /tmp/h.out echo "CONTENDER (exit=$CR)" echo echo "=== fcntl.flock() inter-thread (same process) ===" cat > /tmp/flock_thr.py << 'PYEOF' import fcntl, os, sys, threading, time path = sys.argv[1] N_THR = 8; N_ITER = 100 counter = [0] def w(t): for _ in range(N_ITER): lf = open(path, "w") for _ in range(1000): try: fcntl.flock(lf.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB); break except BlockingIOError: time.sleep(0.001) v = counter[0]; time.sleep(0.0005); counter[0] = v + 1 fcntl.flock(lf.fileno(), fcntl.LOCK_UN); lf.close() threads = [threading.Thread(target=w, args=(i,)) for i in range(N_THR)] [t.start() for t in threads]; [t.join() for t in threads] exp = N_THR * N_ITER print(f"threads={N_THR} iters={N_ITER} got={counter[0]} expected={exp}") print("RESULT:", "PASS" if counter[0] == exp else "FAIL") sys.exit(0 if counter[0] == exp else 1) PYEOF rm -f /data/flocktest python3 /tmp/flock_thr.py /data/flocktest echo echo "=== trackio pre-fix multi-thread write on /data ===" export TRACKIO_DIR=/data/trackio-prefix-test export SYSTEM=spaces rm -rf "$TRACKIO_DIR" && mkdir -p "$TRACKIO_DIR" python3 -c "import trackio; print('trackio loaded OK')" python3 /app/hammer.py echo echo "=== cleanup ===" rm -f /data/locktest.bin /data/flocktest rm -rf "$TRACKIO_DIR" echo "DONE" ) 2>&1 | tee "$RESULT_FILE" echo "Result file: $RESULT_FILE" wait $SERVER_PID