Files
cleveragents-core/features/steps/lsp_lifecycle_coverage_steps.py
freemo 17e7507a4d
CI / lint (pull_request) Successful in 20s
CI / typecheck (pull_request) Successful in 47s
CI / quality (pull_request) Successful in 37s
CI / security (pull_request) Successful in 1m0s
CI / build (pull_request) Successful in 27s
CI / helm (pull_request) Successful in 24s
CI / unit_tests (pull_request) Successful in 6m50s
CI / e2e_tests (pull_request) Successful in 16m59s
CI / coverage (pull_request) Successful in 10m54s
CI / docker (pull_request) Successful in 1m39s
CI / integration_tests (pull_request) Successful in 23m0s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 56m47s
fix(lsp): release lock before blocking I/O in LspLifecycleManager.restart_server() to prevent deadlock
Refactored restart_server() to use the same 3-phase lock pattern already
employed by start_server(), eliminating a deadlock hazard that occurred when
the method held _lock across blocking I/O operations (transport.stop(),
transport.start(), and client.initialize()).

Phase 1 (short lock): reads current state, snapshots required fields, and
removes the old server entry so concurrent callers see the server as absent
during the restart window.

Phase 2 (no lock): stops the old transport, spawns the new process, and
performs the LSP handshake — all without holding _lock. These operations
can block for up to 60 seconds; holding the lock here would starve any
concurrent health_check(), list_running(), or stop_server() calls.

Phase 3 (short lock): commits the new _ManagedServer into shared state,
preserving the original ref_count.

Three new BDD scenarios added to features/lsp_lifecycle_coverage.feature:
- health_check is not blocked while restart_server is in progress
  (uses a threading.Barrier to synchronise the two threads at the exact
  moment the lock is released)
- restart_server does not hold the lock during client.initialize
  (inspects lock state from within the mock initialize() side-effect)
- restart_server preserves the ref_count of the managed server

ISSUES CLOSED: #3026
2026-04-05 07:06:53 +00:00

773 lines
28 KiB
Python

"""Step definitions for lsp_lifecycle_coverage.feature.
Exercises every uncovered code path in cleveragents.lsp.lifecycle including
_ManagedServer construction, start/stop/restart lifecycle, race conditions,
health checks, error handling, stop_all, and list_running.
Also covers the 3-phase lock pattern in restart_server() to verify that
health_check() and other operations are not blocked during blocking I/O.
All external I/O (StdioTransport, LspClient) is mocked so no real
subprocesses are spawned.
"""
from __future__ import annotations
import threading
from unittest.mock import MagicMock, patch
from behave import given, then, when
from behave.runner import Context
from cleveragents.lsp.errors import LspServerNotFoundError
from cleveragents.lsp.lifecycle import LspLifecycleManager, _ManagedServer
from cleveragents.lsp.models import LspServerConfig
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_config(name: str, command: str) -> LspServerConfig:
"""Create a minimal LspServerConfig for testing."""
return LspServerConfig(name=name, command=command)
def _make_mock_transport(alive: bool = True) -> MagicMock:
"""Return a mock StdioTransport that claims to be alive."""
t = MagicMock()
t.is_alive = alive
t.start = MagicMock()
t.stop = MagicMock()
return t
def _make_mock_client(initialized: bool = True) -> MagicMock:
"""Return a mock LspClient."""
c = MagicMock()
c.is_initialized = initialized
c.initialize = MagicMock()
c.shutdown = MagicMock()
return c
def _patch_transport_and_client(context: Context, transport=None, client=None):
"""Patch StdioTransport and LspClient constructors.
Returns (mock_transport_instance, mock_client_instance).
"""
mock_transport = transport or _make_mock_transport()
mock_client = client or _make_mock_client()
p_transport = patch(
"cleveragents.lsp.lifecycle.StdioTransport",
return_value=mock_transport,
)
p_client = patch(
"cleveragents.lsp.lifecycle.LspClient",
return_value=mock_client,
)
p_transport.start()
p_client.start()
context.add_cleanup(p_transport.stop)
context.add_cleanup(p_client.stop)
return mock_transport, mock_client
class _RaceInjectingDict(dict):
"""A dict subclass that injects a race winner on the second .get() call
for a particular key, simulating another thread registering a server
between Phase 1 and Phase 3 of start_server.
"""
def __init__(self, *args, target_key=None, race_winner=None, **kwargs):
super().__init__(*args, **kwargs)
self._target_key = target_key
self._race_winner = race_winner
self._get_count = 0
def get(self, key, default=None):
result = super().get(key, default)
if key == self._target_key:
self._get_count += 1
if self._get_count == 2:
# Simulate another thread having registered this key
self[key] = self._race_winner
return self._race_winner
return result
# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("llcov a fresh LspLifecycleManager instance")
def step_fresh_manager(context: Context) -> None:
context.llcov_manager = LspLifecycleManager()
context.llcov_config: LspServerConfig | None = None
context.llcov_config2: LspServerConfig | None = None
context.llcov_client = None
context.llcov_original_client = None
context.llcov_error = None
context.llcov_result = None
context.llcov_health = None
context.llcov_running_list = None
context.llcov_mock_transport = None
context.llcov_mock_client = None
context.llcov_old_transport = None
context.llcov_race_transport = None
context.llcov_race_client = None
context.llcov_new_transport_stopped = False
# ---------------------------------------------------------------------------
# Config creation
# ---------------------------------------------------------------------------
@given('llcov a server config named "{name}" with command "{command}"')
def step_create_config(context: Context, name: str, command: str) -> None:
cfg = _make_config(name, command)
# If first config already set, store as config2
if context.llcov_config is not None:
context.llcov_config2 = cfg
else:
context.llcov_config = cfg
# ---------------------------------------------------------------------------
# Start server (happy path)
# ---------------------------------------------------------------------------
@given('llcov the server is already started for workspace "{workspace}"')
def step_server_already_started(context: Context, workspace: str) -> None:
mock_transport, mock_client = _patch_transport_and_client(context)
context.llcov_mock_transport = mock_transport
context.llcov_mock_client = mock_client
context.llcov_original_client = context.llcov_manager.start_server(
context.llcov_config, workspace
)
@given('llcov the second server is already started for workspace "{workspace}"')
def step_second_server_already_started(context: Context, workspace: str) -> None:
mock_transport2 = _make_mock_transport()
mock_client2 = _make_mock_client()
p_transport = patch(
"cleveragents.lsp.lifecycle.StdioTransport",
return_value=mock_transport2,
)
p_client = patch(
"cleveragents.lsp.lifecycle.LspClient",
return_value=mock_client2,
)
p_transport.start()
p_client.start()
context.add_cleanup(p_transport.stop)
context.add_cleanup(p_client.stop)
context.llcov_manager.start_server(context.llcov_config2, workspace)
@when('llcov I start the server for workspace "{workspace}"')
def step_start_server(context: Context, workspace: str) -> None:
mock_transport, mock_client = _patch_transport_and_client(context)
context.llcov_mock_transport = mock_transport
context.llcov_mock_client = mock_client
context.llcov_client = context.llcov_manager.start_server(
context.llcov_config, workspace
)
# ---------------------------------------------------------------------------
# Start server: reuse existing alive server
# ---------------------------------------------------------------------------
@given("llcov the server is started again to bump ref count")
def step_bump_ref_count(context: Context) -> None:
"""Start the server a second time to bump ref_count to 2."""
context.llcov_manager.start_server(context.llcov_config, "/tmp/ws")
@when('llcov I start the server again for workspace "{workspace}"')
def step_start_server_again(context: Context, workspace: str) -> None:
# The existing transport is still alive, so Phase 1 should return existing client
context.llcov_client = context.llcov_manager.start_server(
context.llcov_config, workspace
)
# ---------------------------------------------------------------------------
# Start server: initialize failure
# ---------------------------------------------------------------------------
@given("llcov the LspClient initialize will raise an error")
def step_client_init_will_fail(context: Context) -> None:
context.llcov_init_fails = True
@when('llcov I attempt to start the server for workspace "{workspace}"')
def step_attempt_start_server(context: Context, workspace: str) -> None:
mock_transport = _make_mock_transport()
mock_client = _make_mock_client()
mock_client.initialize.side_effect = RuntimeError("init boom")
p_transport = patch(
"cleveragents.lsp.lifecycle.StdioTransport",
return_value=mock_transport,
)
p_client = patch(
"cleveragents.lsp.lifecycle.LspClient",
return_value=mock_client,
)
p_transport.start()
p_client.start()
context.add_cleanup(p_transport.stop)
context.add_cleanup(p_client.stop)
context.llcov_mock_transport = mock_transport
context.llcov_mock_client = mock_client
try:
context.llcov_client = context.llcov_manager.start_server(
context.llcov_config, workspace
)
except Exception as exc:
context.llcov_error = exc
# ---------------------------------------------------------------------------
# Start server: race condition
# ---------------------------------------------------------------------------
@given('llcov another thread already registered "{name}" before Phase 3')
def step_setup_race_condition(context: Context, name: str) -> None:
"""Pre-build a race winner that will be injected into _servers."""
context.llcov_race_transport = _make_mock_transport(alive=True)
context.llcov_race_client = _make_mock_client()
@when('llcov I start the server for workspace "{workspace}" with race')
def step_start_server_with_race(context: Context, workspace: str) -> None:
"""Simulate start_server where another thread wins the race in Phase 3.
We replace ``manager._servers`` with a ``_RaceInjectingDict`` that
injects the race winner on the second ``.get()`` call (Phase 3),
while the first ``.get()`` (Phase 1) returns None normally.
"""
phase2_transport = _make_mock_transport(alive=True)
phase2_client = _make_mock_client()
race_winner = _ManagedServer(
config=context.llcov_config,
transport=context.llcov_race_transport,
client=context.llcov_race_client,
workspace_path=workspace,
)
# Replace _servers with our race-injecting dict
race_dict = _RaceInjectingDict(
target_key=context.llcov_config.name,
race_winner=race_winner,
)
context.llcov_manager._servers = race_dict
p_transport = patch(
"cleveragents.lsp.lifecycle.StdioTransport",
return_value=phase2_transport,
)
p_client = patch(
"cleveragents.lsp.lifecycle.LspClient",
return_value=phase2_client,
)
p_transport.start()
p_client.start()
context.add_cleanup(p_transport.stop)
context.add_cleanup(p_client.stop)
context.llcov_client = context.llcov_manager.start_server(
context.llcov_config, workspace
)
context.llcov_phase2_transport = phase2_transport
# ---------------------------------------------------------------------------
# Stop server
# ---------------------------------------------------------------------------
@when('llcov I stop the server "{name}"')
def step_stop_server(context: Context, name: str) -> None:
context.llcov_manager.stop_server(name)
@when('llcov I attempt to stop server "{name}"')
def step_attempt_stop_server(context: Context, name: str) -> None:
try:
context.llcov_manager.stop_server(name)
except Exception as exc:
context.llcov_error = exc
# ---------------------------------------------------------------------------
# Shutdown (direct call)
# ---------------------------------------------------------------------------
@given("llcov the client shutdown will raise an exception")
def step_client_shutdown_will_fail(context: Context) -> None:
context.llcov_mock_client.shutdown.side_effect = RuntimeError("shutdown boom")
@given("llcov the transport stop will raise an exception")
def step_transport_stop_will_fail(context: Context) -> None:
context.llcov_mock_transport.stop.side_effect = RuntimeError("transport stop boom")
@when("llcov I trigger shutdown on the managed server")
def step_trigger_shutdown(context: Context) -> None:
name = context.llcov_config.name
managed = context.llcov_manager._servers[name]
context.llcov_manager._shutdown_server(managed)
# ---------------------------------------------------------------------------
# Get client
# ---------------------------------------------------------------------------
@when('llcov I get the client for "{name}"')
def step_get_client(context: Context, name: str) -> None:
context.llcov_client = context.llcov_manager.get_client(name)
@when('llcov I attempt to get the client for "{name}"')
def step_attempt_get_client(context: Context, name: str) -> None:
try:
context.llcov_client = context.llcov_manager.get_client(name)
except Exception as exc:
context.llcov_error = exc
# ---------------------------------------------------------------------------
# Health check
# ---------------------------------------------------------------------------
@when('llcov I health check "{name}"')
def step_health_check(context: Context, name: str) -> None:
context.llcov_health = context.llcov_manager.health_check(name)
# ---------------------------------------------------------------------------
# Restart server
# ---------------------------------------------------------------------------
@when('llcov I restart the server "{name}"')
def step_restart_server(context: Context, name: str) -> None:
context.llcov_old_transport = context.llcov_mock_transport
new_transport = _make_mock_transport()
new_client = _make_mock_client()
context.llcov_new_transport = new_transport
context.llcov_new_client = new_client
p_transport = patch(
"cleveragents.lsp.lifecycle.StdioTransport",
return_value=new_transport,
)
p_client = patch(
"cleveragents.lsp.lifecycle.LspClient",
return_value=new_client,
)
p_transport.start()
p_client.start()
context.add_cleanup(p_transport.stop)
context.add_cleanup(p_client.stop)
context.llcov_client = context.llcov_manager.restart_server(name)
@when('llcov I attempt to restart server "{name}"')
def step_attempt_restart_server(context: Context, name: str) -> None:
try:
# For "not found" case, don't set up mocks
if not context.llcov_manager._servers.get(name):
context.llcov_manager.restart_server(name)
else:
# For init failure during restart
context.llcov_old_transport = context.llcov_mock_transport
new_transport = _make_mock_transport()
new_client = _make_mock_client()
new_client.initialize.side_effect = RuntimeError("restart init boom")
context.llcov_new_restart_transport = new_transport
p_transport = patch(
"cleveragents.lsp.lifecycle.StdioTransport",
return_value=new_transport,
)
p_client = patch(
"cleveragents.lsp.lifecycle.LspClient",
return_value=new_client,
)
p_transport.start()
p_client.start()
context.add_cleanup(p_transport.stop)
context.add_cleanup(p_client.stop)
context.llcov_client = context.llcov_manager.restart_server(name)
except Exception as exc:
context.llcov_error = exc
@given("llcov the LspClient initialize will raise on restart")
def step_init_fails_on_restart(context: Context) -> None:
context.llcov_init_fails_on_restart = True
# ---------------------------------------------------------------------------
# Stop all
# ---------------------------------------------------------------------------
@when("llcov I stop all servers")
def step_stop_all(context: Context) -> None:
context.llcov_manager.stop_all()
# ---------------------------------------------------------------------------
# List running
# ---------------------------------------------------------------------------
@when("llcov I list running servers")
def step_list_running(context: Context) -> None:
context.llcov_running_list = context.llcov_manager.list_running()
# ---------------------------------------------------------------------------
# Then assertions
# ---------------------------------------------------------------------------
@then("llcov the returned client should not be None")
def step_client_not_none(context: Context) -> None:
assert context.llcov_client is not None, "Expected a client but got None"
@then('llcov the server "{name}" should be registered')
def step_server_registered(context: Context, name: str) -> None:
assert name in context.llcov_manager._servers, (
f"Server '{name}' not found in _servers"
)
@then('llcov the server "{name}" should not be registered')
def step_server_not_registered(context: Context, name: str) -> None:
assert name not in context.llcov_manager._servers, (
f"Server '{name}' should not be in _servers"
)
@then('llcov the server "{name}" should still be registered')
def step_server_still_registered(context: Context, name: str) -> None:
assert name in context.llcov_manager._servers, (
f"Server '{name}' should still be in _servers"
)
@then("llcov the returned client should be the same as the original")
def step_client_same_as_original(context: Context) -> None:
assert context.llcov_client is context.llcov_original_client, (
"Expected reused client to be the same object"
)
@then('llcov the ref count for "{name}" should be {count:d}')
def step_ref_count(context: Context, name: str, count: int) -> None:
managed = context.llcov_manager._servers[name]
assert managed.ref_count == count, (
f"Expected ref_count={count}, got {managed.ref_count}"
)
@then("llcov a start error should have been raised")
def step_start_error_raised(context: Context) -> None:
assert context.llcov_error is not None, "Expected an error but none was raised"
assert isinstance(context.llcov_error, RuntimeError), (
f"Expected RuntimeError, got {type(context.llcov_error)}"
)
@then("llcov the transport should have been stopped after init failure")
def step_transport_stopped_after_init(context: Context) -> None:
context.llcov_mock_transport.stop.assert_called()
@then("llcov the returned client should be the race winner client")
def step_client_is_race_winner(context: Context) -> None:
assert context.llcov_client is context.llcov_race_client, (
"Expected the race winner client"
)
@then("llcov the losing transport should have been stopped")
def step_losing_transport_stopped(context: Context) -> None:
context.llcov_phase2_transport.stop.assert_called()
@then("llcov shutdown should have been called on the client")
def step_shutdown_called(context: Context) -> None:
context.llcov_mock_client.shutdown.assert_called()
@then("llcov a LspServerNotFoundError should have been raised")
def step_not_found_error(context: Context) -> None:
assert context.llcov_error is not None, "Expected an error but none was raised"
assert isinstance(context.llcov_error, LspServerNotFoundError), (
f"Expected LspServerNotFoundError, got {type(context.llcov_error)}"
)
@then("llcov the client shutdown method should have been called")
def step_client_shutdown_called(context: Context) -> None:
context.llcov_mock_client.shutdown.assert_called()
@then("llcov the transport stop method should have been called")
def step_transport_stop_called(context: Context) -> None:
context.llcov_mock_transport.stop.assert_called()
@then("llcov the transport stop method should have been called despite error")
def step_transport_stop_called_despite_error(context: Context) -> None:
context.llcov_mock_transport.stop.assert_called()
@then("llcov the health check result should be true")
def step_health_true(context: Context) -> None:
assert context.llcov_health is True, f"Expected True, got {context.llcov_health}"
@then("llcov the health check result should be false")
def step_health_false(context: Context) -> None:
assert context.llcov_health is False, f"Expected False, got {context.llcov_health}"
@then("llcov the old transport should have been stopped")
def step_old_transport_stopped(context: Context) -> None:
context.llcov_old_transport.stop.assert_called()
@then("llcov the managed server should have the new transport and client")
def step_managed_has_new(context: Context) -> None:
name = context.llcov_config.name
managed = context.llcov_manager._servers[name]
assert managed.transport is context.llcov_new_transport, (
"Managed server should have the new transport"
)
assert managed.client is context.llcov_new_client, (
"Managed server should have the new client"
)
@then("llcov a restart error should have been raised")
def step_restart_error_raised(context: Context) -> None:
assert context.llcov_error is not None, "Expected an error but none was raised"
assert isinstance(context.llcov_error, RuntimeError), (
f"Expected RuntimeError, got {type(context.llcov_error)}"
)
@then("llcov the new transport should have been stopped after restart failure")
def step_new_transport_stopped_after_restart(context: Context) -> None:
context.llcov_new_restart_transport.stop.assert_called()
@then("llcov no servers should be registered")
def step_no_servers(context: Context) -> None:
assert len(context.llcov_manager._servers) == 0, (
f"Expected 0 servers, got {len(context.llcov_manager._servers)}"
)
@then("llcov the running list should contain {count:d} entry")
def step_running_list_count_singular(context: Context, count: int) -> None:
assert len(context.llcov_running_list) == count, (
f"Expected {count} entries, got {len(context.llcov_running_list)}"
)
@then("llcov the running list should contain {count:d} entries")
def step_running_list_count_plural(context: Context, count: int) -> None:
assert len(context.llcov_running_list) == count, (
f"Expected {count} entries, got {len(context.llcov_running_list)}"
)
@then('llcov the running entry for "{name}" should have workspace "{workspace}"')
def step_running_entry_workspace(context: Context, name: str, workspace: str) -> None:
entry = next((e for e in context.llcov_running_list if e["name"] == name), None)
assert entry is not None, f"No entry found for '{name}'"
assert entry["workspace"] == workspace, (
f"Expected workspace='{workspace}', got '{entry['workspace']}'"
)
@then('llcov the running entry for "{name}" should have alive status true')
def step_running_entry_alive(context: Context, name: str) -> None:
entry = next((e for e in context.llcov_running_list if e["name"] == name), None)
assert entry is not None, f"No entry found for '{name}'"
assert entry["alive"] is True, f"Expected alive=True, got {entry['alive']}"
@then('llcov the running entry for "{name}" should have ref count {count:d}')
def step_running_entry_ref_count(context: Context, name: str, count: int) -> None:
entry = next((e for e in context.llcov_running_list if e["name"] == name), None)
assert entry is not None, f"No entry found for '{name}'"
assert entry["ref_count"] == count, (
f"Expected ref_count={count}, got {entry['ref_count']}"
)
# ---------------------------------------------------------------------------
# Concurrent-access tests for 3-phase lock pattern in restart_server()
# ---------------------------------------------------------------------------
@when(
"llcov I restart the server concurrently and health_check runs during blocking IO"
)
def step_restart_concurrent_health_check(context: Context) -> None:
"""Verify health_check() is not blocked while restart_server() is in progress.
We inject a barrier into the mock ``initialize`` call so that the
restart_server() thread pauses in Phase 2 (blocking I/O, no lock held).
A second thread then calls health_check() and records whether it
completed without blocking.
"""
# Barrier: both threads must reach it before either proceeds.
# Thread 1 (restart): reaches barrier inside initialize()
# Thread 2 (health_check): reaches barrier after acquiring the lock
barrier = threading.Barrier(2, timeout=5)
health_check_completed = threading.Event()
health_check_result: list[bool] = []
new_transport = _make_mock_transport()
new_client = _make_mock_client()
def _blocking_initialize(workspace_path: str) -> None:
"""Simulate a slow LSP handshake; signal the barrier mid-way."""
barrier.wait() # Signal: "I'm in Phase 2, lock is released"
new_client.initialize.side_effect = _blocking_initialize
def _restart_thread() -> None:
p_transport = patch(
"cleveragents.lsp.lifecycle.StdioTransport",
return_value=new_transport,
)
p_client = patch(
"cleveragents.lsp.lifecycle.LspClient",
return_value=new_client,
)
with p_transport, p_client:
context.llcov_restarted_client = context.llcov_manager.restart_server(
context.llcov_config.name
)
def _health_check_thread() -> None:
barrier.wait() # Wait until restart is in Phase 2 (lock released)
# health_check() must acquire _lock; if restart held it, this would block
result = context.llcov_manager.health_check(context.llcov_config.name)
health_check_result.append(result)
health_check_completed.set()
t_restart = threading.Thread(target=_restart_thread, daemon=True)
t_health = threading.Thread(target=_health_check_thread, daemon=True)
t_restart.start()
t_health.start()
t_restart.join(timeout=10)
t_health.join(timeout=10)
context.llcov_health_check_completed = health_check_completed.is_set()
context.llcov_health_check_result_concurrent = health_check_result
context.llcov_new_transport = new_transport
context.llcov_new_client = new_client
@then("llcov health_check should have completed without blocking")
def step_health_check_completed(context: Context) -> None:
assert context.llcov_health_check_completed, (
"health_check() did not complete — it was blocked while restart_server() "
"held the lock during blocking I/O"
)
@then("llcov the restarted client should not be None")
def step_restarted_client_not_none(context: Context) -> None:
assert context.llcov_restarted_client is not None, (
"Expected a restarted client but got None"
)
@when("llcov I restart the server and verify lock is free during initialize")
def step_restart_verify_lock_free(context: Context) -> None:
"""Assert that _lock is NOT held when client.initialize() is called.
We inspect the lock state from within the mock initialize() call.
If the lock is held, ``_lock.acquire(blocking=False)`` returns False.
"""
lock_was_free_during_init: list[bool] = []
manager = context.llcov_manager
new_transport = _make_mock_transport()
new_client = _make_mock_client()
def _check_lock_initialize(workspace_path: str) -> None:
# Try to acquire the lock non-blocking; succeeds only if not held
acquired = manager._lock.acquire(blocking=False)
lock_was_free_during_init.append(acquired)
if acquired:
manager._lock.release()
new_client.initialize.side_effect = _check_lock_initialize
p_transport = patch(
"cleveragents.lsp.lifecycle.StdioTransport",
return_value=new_transport,
)
p_client = patch(
"cleveragents.lsp.lifecycle.LspClient",
return_value=new_client,
)
with p_transport, p_client:
context.llcov_restarted_client = context.llcov_manager.restart_server(
context.llcov_config.name
)
context.llcov_lock_was_free_during_init = lock_was_free_during_init
context.llcov_new_transport = new_transport
context.llcov_new_client = new_client
@then("llcov the lock should have been acquirable during initialize")
def step_lock_free_during_init(context: Context) -> None:
results = context.llcov_lock_was_free_during_init
assert results, "initialize() was never called — cannot verify lock state"
assert all(results), (
"The lock was held during client.initialize() — restart_server() "
"must not hold _lock during blocking I/O"
)