Skip to content

Commit e5c9a09

Browse files
committed
Finalize Lean declaration sessions
1 parent 5c179ea commit e5c9a09

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

src/jacobian/lean_frontend/declarations.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import tempfile
99
import threading
1010
import uuid
11+
import weakref
1112
from dataclasses import dataclass
1213
from pathlib import Path
1314
from typing import Any, Protocol
@@ -290,6 +291,16 @@ class _SessionEntry:
290291
session: _DeclarationQuerySession
291292

292293

294+
def _close_declaration_sessions(
295+
sessions: dict[LeanEnvironment, _SessionEntry],
296+
session_locks: dict[LeanEnvironment, threading.Lock],
297+
) -> None:
298+
"""Release declaration sessions retained past their backend's lifetime."""
299+
for environment, entry in list(sessions.items()):
300+
with session_locks[environment]:
301+
entry.session.close()
302+
303+
293304
class LeanSubprocessDeclarationBackend:
294305
"""Reuse one catalog across bounded processes for each validated environment."""
295306

@@ -312,6 +323,12 @@ def __init__(
312323
self._session_locks = {
313324
environment: threading.Lock() for environment in LeanEnvironment
314325
}
326+
self._finalizer = weakref.finalize(
327+
self,
328+
_close_declaration_sessions,
329+
self._sessions,
330+
self._session_locks,
331+
)
315332

316333
def environment_digest(self, environment: LeanEnvironment) -> str:
317334
try:
@@ -450,6 +467,7 @@ def close(self) -> None:
450467
for environment, lock in self._session_locks.items():
451468
with lock:
452469
self._discard_session(environment)
470+
self._finalizer.detach()
453471

454472
def _start_session(
455473
self,

tests/component/providers/lean/test_lean_declaration_sessions.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
from __future__ import annotations
44

5+
import gc
56
import hashlib
67
import threading
8+
import weakref
79
from concurrent.futures import ThreadPoolExecutor
810
from dataclasses import dataclass, field
911
from pathlib import Path
@@ -152,6 +154,32 @@ def test_subprocess_backend_reuses_one_pinned_session_per_environment(
152154
assert session.closed
153155

154156

157+
def test_subprocess_backend_finalizer_closes_sessions_after_garbage_collection(
158+
tmp_path: Path,
159+
) -> None:
160+
session = RecordingSession(responses=[{"operation": "search", "declarations": []}])
161+
backend, _ = _recording_backend(tmp_path, [session])
162+
backend.query(LeanEnvironment.CORE, {"operation": "search"})
163+
reference = weakref.ref(backend)
164+
165+
del backend
166+
gc.collect()
167+
168+
assert reference() is None
169+
assert session.closed
170+
171+
172+
def test_subprocess_backend_close_detaches_finalizer(tmp_path: Path) -> None:
173+
session = RecordingSession(responses=[{"operation": "search", "declarations": []}])
174+
backend, _ = _recording_backend(tmp_path, [session])
175+
backend.query(LeanEnvironment.CORE, {"operation": "search"})
176+
177+
backend.close()
178+
179+
assert session.closed
180+
assert not backend._finalizer.alive
181+
182+
155183
def test_subprocess_backend_serializes_concurrent_session_requests(
156184
tmp_path: Path,
157185
) -> None:

0 commit comments

Comments
 (0)