Merge pull request #178 from nesquena/fix/streaming-env-lock-deadlock
fix: resolve _ENV_LOCK deadlock that blocks chat after first message
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -5,6 +5,17 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [v0.39.1] — 2026-04-08
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- **_ENV_LOCK deadlock resolved.** The environment variable lock was held for
|
||||||
|
the entire duration of agent execution (including all tool calls and streaming),
|
||||||
|
blocking all concurrent requests. Now the lock is acquired only for the brief
|
||||||
|
env variable read/write operations, released before the agent runs, and
|
||||||
|
re-acquired in the finally block for restoration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [v0.39.0] — 2026-04-08
|
## [v0.39.0] — 2026-04-08
|
||||||
|
|
||||||
### Security (12 fixes — PR #171 by @betamod, reviewed by @nesquena-hermes)
|
### Security (12 fixes — PR #171 by @betamod, reviewed by @nesquena-hermes)
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ def _run_agent_streaming(session_id, msg_text, model, workspace, stream_id, atta
|
|||||||
HERMES_HOME=_profile_home,
|
HERMES_HOME=_profile_home,
|
||||||
)
|
)
|
||||||
# Still set process-level env as fallback for tools that bypass thread-local
|
# Still set process-level env as fallback for tools that bypass thread-local
|
||||||
|
# Acquire lock only for the env mutation, then release before the agent runs.
|
||||||
|
# The finally block re-acquires to restore — keeping critical sections short
|
||||||
|
# and preventing a deadlock where the restore would re-enter the same lock.
|
||||||
with _ENV_LOCK:
|
with _ENV_LOCK:
|
||||||
old_cwd = os.environ.get('TERMINAL_CWD')
|
old_cwd = os.environ.get('TERMINAL_CWD')
|
||||||
old_exec_ask = os.environ.get('HERMES_EXEC_ASK')
|
old_exec_ask = os.environ.get('HERMES_EXEC_ASK')
|
||||||
@@ -117,7 +120,7 @@ def _run_agent_streaming(session_id, msg_text, model, workspace, stream_id, atta
|
|||||||
os.environ['HERMES_SESSION_KEY'] = session_id
|
os.environ['HERMES_SESSION_KEY'] = session_id
|
||||||
if _profile_home:
|
if _profile_home:
|
||||||
os.environ['HERMES_HOME'] = _profile_home
|
os.environ['HERMES_HOME'] = _profile_home
|
||||||
|
# Lock released — agent runs without holding it
|
||||||
try:
|
try:
|
||||||
def on_token(text):
|
def on_token(text):
|
||||||
if text is None:
|
if text is None:
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="sidebar-header"><div class="logo">H</div><div><h1 style="margin:0;font-size:15px;font-weight:700;letter-spacing:-.01em">Hermes</h1><div style="font-size:10px;color:var(--muted);opacity:.8;margin-top:1px">v0.39.0</div></div></div>
|
<div class="sidebar-header"><div class="logo">H</div><div><h1 style="margin:0;font-size:15px;font-weight:700;letter-spacing:-.01em">Hermes</h1><div style="font-size:10px;color:var(--muted);opacity:.8;margin-top:1px">v0.39.1</div></div></div>
|
||||||
<div class="sidebar-nav">
|
<div class="sidebar-nav">
|
||||||
<button class="nav-tab active" data-panel="chat" data-label="Chat" onclick="switchPanel('chat')" title="Chat">💬</button>
|
<button class="nav-tab active" data-panel="chat" data-label="Chat" onclick="switchPanel('chat')" title="Chat">💬</button>
|
||||||
<button class="nav-tab" data-panel="tasks" data-label="Tasks" onclick="switchPanel('tasks')" title="Tasks">📅</button>
|
<button class="nav-tab" data-panel="tasks" data-label="Tasks" onclick="switchPanel('tasks')" title="Tasks">📅</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user