@@ -154,77 +154,39 @@ async def add(self, fingerprint: AnswerFingerprint) -> None:
154154 self ._exact_hashes [fingerprint .exact_hash ] = fingerprint
155155
156156 def check_sync (self , fingerprint : AnswerFingerprint ) -> FingerprintMatch :
157- """Synchronous wrapper -- for use outside an async context only."""
158- loop : asyncio .AbstractEventLoop | None = None
157+ """Synchronous wrapper -- for use outside an async context only.
158+
159+ Raises ``RuntimeError`` if called from within a running event loop.
160+ Callers inside async contexts MUST use ``await check()`` instead.
161+ """
159162 try :
160- loop = asyncio .get_running_loop ()
163+ asyncio .get_running_loop ()
161164 except RuntimeError :
162- pass
163-
164- if loop is not None and loop .is_running ():
165- # We're inside a running event loop (e.g. pytest-asyncio).
166- # The caller should use ``await check()`` instead. Fall back
167- # to a direct (unlocked) check so sync tests still work.
168- return self ._check_unlocked (fingerprint )
165+ pass # No running loop — safe to use asyncio.run()
166+ else :
167+ raise RuntimeError (
168+ "check_sync() called from a running event loop. "
169+ "Use 'await store.check(fp)' instead."
170+ )
169171 return asyncio .run (self .check (fingerprint ))
170172
171173 def add_sync (self , fingerprint : AnswerFingerprint ) -> None :
172- """Synchronous wrapper -- for use outside an async context only."""
173- loop : asyncio .AbstractEventLoop | None = None
174+ """Synchronous wrapper -- for use outside an async context only.
175+
176+ Raises ``RuntimeError`` if called from within a running event loop.
177+ Callers inside async contexts MUST use ``await add()`` instead.
178+ """
174179 try :
175- loop = asyncio .get_running_loop ()
180+ asyncio .get_running_loop ()
176181 except RuntimeError :
177- pass
178-
179- if loop is not None and loop .is_running ():
180- self ._add_unlocked (fingerprint )
181- return
182+ pass # No running loop — safe to use asyncio.run()
183+ else :
184+ raise RuntimeError (
185+ "add_sync() called from a running event loop. "
186+ "Use 'await store.add(fp)' instead."
187+ )
182188 asyncio .run (self .add (fingerprint ))
183189
184- # ------------------------------------------------------------------
185- # Internal helpers used by sync wrappers (no lock, no await)
186- # ------------------------------------------------------------------
187-
188- def _check_unlocked (self , fingerprint : AnswerFingerprint ) -> FingerprintMatch :
189- """Lock-free check -- only safe when no concurrent access."""
190- if fingerprint .exact_hash in self ._exact_hashes :
191- existing = self ._exact_hashes [fingerprint .exact_hash ]
192- if existing .agent_did != fingerprint .agent_did :
193- return FingerprintMatch (
194- is_exact_duplicate = True ,
195- hamming_distance = 0 ,
196- matching_session_id = existing .session_id ,
197- matching_agent_did = existing .agent_did ,
198- )
199-
200- for stored in self ._fingerprints :
201- if stored .agent_did == fingerprint .agent_did :
202- continue
203- if stored .question_hash != fingerprint .question_hash :
204- continue
205-
206- dist = hamming_distance (fingerprint .simhash , stored .simhash )
207- if dist <= self ._hamming_threshold :
208- return FingerprintMatch (
209- is_near_duplicate = True ,
210- hamming_distance = dist ,
211- matching_session_id = stored .session_id ,
212- matching_agent_did = stored .agent_did ,
213- )
214-
215- return FingerprintMatch ()
216-
217- def _add_unlocked (self , fingerprint : AnswerFingerprint ) -> None :
218- """Lock-free add -- only safe when no concurrent access."""
219- if len (self ._fingerprints ) >= self ._window_size :
220- evicted = self ._fingerprints [0 ]
221- stored = self ._exact_hashes .get (evicted .exact_hash )
222- if stored is not None and stored .session_id == evicted .session_id :
223- del self ._exact_hashes [evicted .exact_hash ]
224-
225- self ._fingerprints .append (fingerprint )
226- self ._exact_hashes [fingerprint .exact_hash ] = fingerprint
227-
228190 def build_fingerprint (
229191 self ,
230192 session_id : str ,
0 commit comments