-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Title: [Bug]: SyncOpenViking.search(session=sess) deadlocks (hangs indefinitely)
Bug Description
SyncOpenViking.search() hangs forever when a session parameter is provided. Normal search() (without session) and find() work fine.
Root Cause
Traced through source code:
SyncOpenViking.search()→run_async(async_client.search(...))— schedules coroutine on the shared background loop thread- →
async_client.search()→local.py search()→session.get_context_for_search(query)(sync method) - →
Session.get_context_for_search()internally callsrun_async(self._viking_fs.ls(...))andrun_async(self._viking_fs.read_file(...)) - These nested
run_async()calls submit new coroutines to the same shared loop viarun_coroutine_threadsafe()and block with.result() - But the shared loop thread is already occupied executing step 1's coroutine → deadlock: loop thread waiting on itself
Main Thread Shared Loop Thread
│ │
├─ run_async(search()) │
│ └─ future.result() ────────►│─ executing search() coroutine
│ (blocking main) │ ├─ session.get_context_for_search()
│ │ │ └─ run_async(viking_fs.ls())
│ │ │ └─ future.result() ← BLOCKED
│ │ │ (waiting for loop thread,
│ │ │ but loop thread is here!)
│ │ │
│ │ └─ 💀 DEADLOCK
Steps to Reproduce
Minimal script (no external dependencies beyond openviking):
import asyncio, threading
from openviking_cli.utils.async_utils import _get_loop, run_async
# Simulates the exact call pattern: async function calls sync method that calls run_async
async def outer():
print(f"outer() on thread: {threading.get_ident()}")
def sync_inner(): # Like session.get_context_for_search()
print(f"sync_inner() on thread: {threading.get_ident()}")
async def dummy():
await asyncio.sleep(0.01)
return "ok"
return run_async(dummy()) # Submits to same loop → deadlock
return sync_inner()
# This will hang forever
result = run_async(outer())Full OpenViking reproduction:
import os
os.environ['OPENVIKING_CONFIG_FILE'] = './ov.conf'
import openviking as ov
from openviking.message.part import TextPart
client = ov.SyncOpenViking(path='./data')
client.initialize()
# This works fine:
r1 = client.search("test query", limit=3)
print(f"search() without session: OK, {len(r1.resources)} results")
# This hangs forever:
sess_info = client.create_session()
sess = client.session(sess_info['session_id'])
sess.add_message('user', [TextPart("test query")])
r2 = client.search("test query", session=sess, limit=3) # ← never returnsWorkaround
Use AsyncOpenViking with asyncio.run() instead. The nested run_async() calls go to the shared background loop, while the outer coroutine runs on a separate temporary loop — no deadlock:
import asyncio
import openviking as ov
from openviking.message.part import TextPart
async def search_with_session():
client = ov.AsyncOpenViking(path='./data')
await client.initialize()
sess_info = await client.create_session()
sess = client.session(sess_info['session_id'])
sess.add_message('user', [TextPart("test query")])
result = await client.search("test query", session=sess, limit=3)
return result
result = asyncio.run(search_with_session()) # Works!Suggested Fix
Make Session.get_context_for_search() async, or at minimum avoid calling run_async() from within a coroutine already running on the shared loop. The sync Session methods (load(), commit(), get_context_for_search()) all use run_async() internally, which is safe when called from the main thread but deadlocks when called from within the shared loop's own coroutines.
Environment
- openviking: 0.1.17
- Python: 3.12
- OS: Ubuntu 24.04
- Reproduced in both production and clean (empty data directory) environments
Metadata
Metadata
Assignees
Labels
Type
Projects
Status