Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 8 additions & 1 deletion liveweb_arena/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@

# Core components
from .core.models import BrowserObservation, BrowserAction, CompositeTask, TrajectoryStep
from .core.browser import BrowserEngine, BrowserSession
from .plugins.base import BasePlugin, SubTask, ValidationResult

# Optional browser layer (depends on playwright). Keep package importable for
# non-browser utilities (e.g., redteam/reporting) in minimal environments.
try:
from .core.browser import BrowserEngine, BrowserSession # type: ignore
except ModuleNotFoundError:
BrowserEngine = None # type: ignore
BrowserSession = None # type: ignore

__all__ = [
"__version__",
# Models
Expand Down
48 changes: 40 additions & 8 deletions liveweb_arena/core/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
"""

import asyncio
import fcntl
import json
import logging
import os
import re
import sys
import time
from dataclasses import dataclass
from pathlib import Path
Expand All @@ -40,6 +40,13 @@
DEFAULT_TTL = 48 * 3600


_IS_WINDOWS = os.name == "nt"
if not _IS_WINDOWS:
import fcntl # type: ignore
else:
import msvcrt # type: ignore


class CacheFatalError(Exception):
"""
Raised when page caching fails due to network issues.
Expand Down Expand Up @@ -130,26 +137,51 @@ async def async_file_lock_acquire(lock_path: Path, timeout: float = 60.0) -> int
start = time.time()

while True:
fd = open(lock_path, 'w')
fd = open(lock_path, "a+b")
try:
# Try non-blocking lock
fcntl.flock(fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
# Ensure the lock file has at least one byte so Windows locking works reliably.
try:
if fd.tell() == 0:
fd.write(b"\0")
fd.flush()
except Exception:
pass

if _IS_WINDOWS:
# Non-blocking exclusive lock of 1 byte.
msvcrt.locking(fd.fileno(), msvcrt.LK_NBLCK, 1)
else:
# POSIX non-blocking exclusive lock.
fcntl.flock(fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
return fd # Lock acquired, return file object
except BlockingIOError:
fd.close()
except (BlockingIOError, OSError):
# Lock held by another process, wait and retry
try:
fd.close()
except Exception:
pass
if time.time() - start > timeout:
raise TimeoutError(f"Could not acquire lock {lock_path} within {timeout}s")
await asyncio.sleep(0.1) # Yield to event loop
except Exception:
fd.close()
try:
fd.close()
except Exception:
pass
raise


def async_file_lock_release(fd):
"""Release file lock acquired by async_file_lock_acquire()."""
try:
fcntl.flock(fd.fileno(), fcntl.LOCK_UN)
if _IS_WINDOWS:
try:
fd.seek(0)
except Exception:
pass
msvcrt.locking(fd.fileno(), msvcrt.LK_UNLCK, 1)
else:
fcntl.flock(fd.fileno(), fcntl.LOCK_UN)
finally:
fd.close()

Expand Down
8 changes: 8 additions & 0 deletions liveweb_arena/redteam/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Red team utilities for template quality checks."""

from __future__ import annotations

__all__ = ["__version__"]

__version__ = "0.1.0"

Loading