diff --git a/analysis/concurrent_knowledge.py b/analysis/concurrent_knowledge.py index 4706ac9..ee993a0 100644 --- a/analysis/concurrent_knowledge.py +++ b/analysis/concurrent_knowledge.py @@ -5,7 +5,6 @@ vulnerability hypotheses, allowing multiple agents to collaborate on analysis. """ -import fcntl import hashlib import json import threading @@ -17,6 +16,8 @@ from pathlib import Path from typing import Any +import portalocker + # ============================================================================ # Base Concurrent Store # ============================================================================ @@ -47,7 +48,7 @@ def _acquire_lock(self, timeout: float = 10.0) -> Any: while True: try: - fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) + portalocker.lock(lock_file, portalocker.LOCK_EX | portalocker.LOCK_NB) return lock_file except OSError: if time.time() - start_time > timeout: @@ -58,7 +59,7 @@ def _acquire_lock(self, timeout: float = 10.0) -> Any: def _release_lock(self, lock_file: Any): """Release file lock.""" try: - fcntl.flock(lock_file, fcntl.LOCK_UN) + portalocker.unlock(lock_file) lock_file.close() if self.lock_path.exists(): self.lock_path.unlink() diff --git a/commands/project.py b/commands/project.py index b7e4d3c..a0aacd6 100644 --- a/commands/project.py +++ b/commands/project.py @@ -225,8 +225,9 @@ def get_project(self, name: str) -> dict | None: if not config_file.exists(): return None - import fcntl import time + + import portalocker # Retry logic for reading JSON with file locking max_retries = 5 @@ -235,9 +236,9 @@ def get_project(self, name: str) -> dict | None: with open(config_file) as f: # Try to get a shared lock for reading try: - fcntl.flock(f.fileno(), fcntl.LOCK_SH | fcntl.LOCK_NB) + portalocker.lock(f.fileno(), portalocker.LOCK_SH | portalocker.LOCK_NB) content = f.read() - fcntl.flock(f.fileno(), fcntl.LOCK_UN) + portalocker.unlock(f.fileno()) except OSError: # If we can't get lock, just read anyway content = f.read() @@ -266,9 +267,9 @@ def get_project(self, name: str) -> dict | None: with open(config_file, 'w') as f: # Try to get exclusive lock for writing try: - fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + portalocker.lock(f.fileno(), portalocker.LOCK_EX | portalocker.LOCK_NB) json.dump(config, f, indent=2) - fcntl.flock(f.fileno(), fcntl.LOCK_UN) + portalocker.unlock(f.fileno()) except OSError: # If we can't get lock, skip updating last_accessed pass diff --git a/requirements.txt b/requirements.txt index ad220cd..a53e2fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,6 @@ typer>=0.9.0 pydantic>=2.0.0 pydantic-settings>=2.0 pyyaml>=6.0 - # Async Support httpx>=0.25.0 @@ -26,3 +25,6 @@ scikit-learn>=1.3.0 # CLI Enhancement rich>=13.0.0 + +# File Locking +portalocker>=2.7.0 \ No newline at end of file