Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions capiscio_sdk/_rpc/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,38 @@

logger = logging.getLogger(__name__)

# Default socket path
# Default socket path — use PID-specific path to avoid contention
# with orphaned capiscio-core processes from previous runs.
DEFAULT_SOCKET_DIR = Path.home() / ".capiscio"
DEFAULT_SOCKET_PATH = DEFAULT_SOCKET_DIR / "rpc.sock"


def _default_socket_path() -> Path:
"""Compute PID-specific socket path lazily at runtime.

This must NOT be computed at import time because forked child processes
would inherit the parent's PID-based path and contend on the same socket.
"""
return DEFAULT_SOCKET_DIR / f"rpc-{os.getpid()}.sock"


def _cleanup_stale_sockets() -> None:
"""Remove rpc-*.sock files whose PID is no longer running."""
try:
for sock in DEFAULT_SOCKET_DIR.glob("rpc-*.sock"):
try:
pid_str = sock.stem.split("-", 1)[1]
pid = int(pid_str)
os.kill(pid, 0) # Check if PID exists
except (ValueError, IndexError):
sock.unlink(missing_ok=True)
except ProcessLookupError:
# PID doesn't exist — stale socket
logger.debug("Removing stale socket %s", sock)
sock.unlink(missing_ok=True)
except PermissionError:
pass # PID exists but owned by another user
except OSError:
pass

# Binary download configuration
CORE_VERSION = "2.5.0"
Expand Down Expand Up @@ -73,7 +102,7 @@ def address(self) -> str:
return self._tcp_address
if self._socket_path:
return f"unix://{self._socket_path}"
return f"unix://{DEFAULT_SOCKET_PATH}"
return f"unix://{_default_socket_path()}"

@property
def is_running(self) -> bool:
Expand Down Expand Up @@ -351,11 +380,14 @@ def _start_unix_socket(
) -> str:
"""Start the gRPC server with a Unix socket listener."""
# Set up socket path
self._socket_path = socket_path or DEFAULT_SOCKET_PATH
self._socket_path = socket_path or _default_socket_path()

# Ensure socket directory exists
self._socket_path.parent.mkdir(parents=True, exist_ok=True)

# Clean up stale sockets from previous runs
_cleanup_stale_sockets()

# Remove stale socket
if self._socket_path.exists():
self._socket_path.unlink()
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@ def test_address_property_returns_unix_by_default(self):
pm = ProcessManager()
pm._tcp_address = None
pm._socket_path = None
from capiscio_sdk._rpc.process import DEFAULT_SOCKET_PATH
assert pm.address == f"unix://{DEFAULT_SOCKET_PATH}"
from capiscio_sdk._rpc.process import _default_socket_path
assert pm.address == f"unix://{_default_socket_path()}"


class TestChecksumVerification:
Expand Down
Loading