Langfuse.shutdown() hangs forever on _score_ingestion_queue.join() — cached LangfuseResourceManager is reused after shutdown
@hassiebp 已经在做这个了。
开始于 2026年8月5日。
评估
这个 Issue 还没有评估数据。
描述
Summary
Langfuse.shutdown() (and flush()) can block forever on self._score_ingestion_queue.join() in LangfuseResourceManager.flush().
Root cause: LangfuseResourceManager instances are cached process-wide by public_key (LangfuseResourceManager._instances), but shutdown() does not remove the instance from the cache. Any Langfuse client constructed afterwards with the same public_key silently receives the already-shut-down manager: its ingestion consumer threads are dead, so any event enqueued after that point (e.g. create_score(), which checks neither _shutdown nor consumer liveness) is never consumed, Queue.unfinished_tasks never reaches 0, and the next flush()/shutdown() blocks forever on Queue.join().
Minimal reproduction
import faulthandler
import threading
from langfuse import Langfuse
faulthandler.dump_traceback_later(10, exit=True)
COMMON_KWARGS = dict(
public_key='pk-lf-repro',
secret_key='sk-lf-repro',
host='http://localhost:9', # unreachable on purpose; nothing is sent in this repro
)
client_a = Langfuse(**COMMON_KWARGS)
client_b = Langfuse(**COMMON_KWARGS)
assert client_a._resources is client_b._resources # same cached resource manager
client_a.shutdown() # stops the shared ingestion consumer threads for good
client_b.create_score(name='quality', value=1.0) # enqueued, but no consumer is alive anymore
print('threads alive:', [t.name for t in threading.enumerate()], flush=True)
print('score queue unfinished tasks:', client_b._resources._score_ingestion_queue.unfinished_tasks, flush=True)
client_b.shutdown() # hangs forever on _score_ingestion_queue.join()
print('UNREACHABLE: shutdown returned', flush=True)
Output (the watchdog kills the process after 10 s; without it the hang is indefinite):
threads alive: ['MainThread', 'OtelBatchSpanRecordProcessor', 'Thread-3']
score queue unfinished tasks: 1
Timeout (0:00:10)!
...
Thread 0x00000001f38bde80 (most recent call first):
File ".../python3.12/threading.py", line 355 in wait
File ".../python3.12/queue.py", line 90 in join
File ".../langfuse/_client/resource_manager.py", line 608 in flush
File ".../langfuse/_client/resource_manager.py", line 620 in shutdown
File ".../langfuse/_client/client.py", line 2340 in shutdown
File "langfuse_deadlock_repro.py", line 34 in <module>
Note that no IngestionConsumer threads are alive when client_b enqueues the score — they were joined by client_a.shutdown().
How we hit this in practice
In a pytest suite, several independently built application instances each create their own Langfuse client with the same public_key and shut it down in their teardown. Because all of them share the single cached LangfuseResourceManager, the first teardown kills the consumer threads for everyone; a later teardown then hangs the whole test process forever on _score_ingestion_queue.join(). The same pattern can hang a production graceful shutdown if two clients with one key exist in a process (e.g. app + sidecar worker, or re-initialization after a config reload).
Expected behavior
Either of:
shutdown()removes the instance fromLangfuseResourceManager._instances, so the nextLangfuse(...)with that key builds a fresh, working manager; and/oradd_score_task()/add_trace_task()refuse to enqueue (or__new__refuses to return a manager) once_shutdownis set; and/orflush()uses a bounded wait instead of a bareQueue.join(), so a lost consumer cannot block shutdown indefinitely.
Environment
langfuse4.14.1- Python 3.12.x (CPython), macOS 15 (arm64) — also reproduced on Linux in CI
- No Langfuse server involved; the hang is purely client-side
- 主要语言
- Python
- 星标
- 468
- 派生
- 349
- 平均合并
- 15 小时 51 分钟
- 30 天内合并 PR
- 24
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
langfuse/langfuse-python 的其他 Issue
-
bug compliance feat-data-masking sdk-python security
langfuse/langfuse-python#1896 · 已指派 1 人 ·
-
feat-scores sdk-python unconfirmed-bug
langfuse/langfuse-python#1890 · 已指派 1 人 ·
-
bug feat-evals sdk-python
langfuse/langfuse-python#1861 · 2 条评论 · 已指派 1 人 ·
-
improvement sdk-python
langfuse/langfuse-python#1856 · 已指派 1 人 ·
-
improvement sdk-python security
langfuse/langfuse-python#1855 · 已指派 1 人 ·
查看 langfuse/langfuse-python 的全部 Issue
相似的 Issue
-
难度 1/5 1 小时以内 新手友好度 75/100
-
hcocena 未关闭policies-accepted pre-review precheck-passed
难度 1/5 1 小时以内 新手友好度 88/100
Bioconductor/BiocContributions#214 · 5 条评论 ·
-
难度 1/5 1 小时以内 新手友好度 92/100
TencentCloud/Octop#1169 · 1 条评论 ·
-
难度 2/5 1-3 小时 新手友好度 70/100
521xueweihan/HelloGitHub#3778 ·
-
The version checker's trailing attribute region has no control for a less-than inside a quoted value 未关闭area: dashboard area: tests bug perceived difficulty: 2 python
难度 2/5 1-3 小时 新手友好度 84/100
Nitjsefnie-Harness-Commons/daedalus#1105 · 1 条评论 ·