mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-12 14:15:14 +00:00
Fix: Tool calls exceeding the timeout blocked the task executor indefinitely;
tasks are now cancelled on timeout and the executor recovers Previously, Tool.apply_ex issued its task without a timeout, so a hung tool would block the TaskExecutor's dispatcher thread forever: the caller received a TimeoutError after tool_timeout, but no further tasks were ever processed. Changes: - Tool.apply_ex now passes the configured tool timeout to issue_task, bounding the dispatcher's wait as well as the caller's. - TaskExecutor.Task.result gained cancel_on_timeout (default True): on timeout, the task is cancelled, preventing execution of tasks still waiting in the queue and discarding the result of tasks already running. - Task.wait_until_done no longer takes a timeout parameter; it uses the task's own timeout and returns whether the task completed. The dispatcher logs a warning and continues with the next task if the timeout was reached. - TaskExecutor.execute_task now propagates its timeout to result() instead of waiting indefinitely, thus also benefiting from cancel-on-timeout. - Corrected the docstring of Task.result to match the actual semantics.
This commit is contained in:
1 parent
d86fd6c0c8
commit
140ffaf3ff
3 files changed
+35
-15
No files matched your search
@@ -3,6 +3,8 @@
|
||||
Status of the `main` branch. Changes prior to the next official version change will appear here.
|
||||
|
||||
* General:
|
||||
- Fix: a tool call exceeding the timeout blocked the task executor indefinitely; the executor now
|
||||
recovers without user-induced cancellation
|
||||
- Add Grok Build support (context `grok`, setup CLI, hooks)
|
||||
- The `languages` key in project configurations was changed to `language_servers` to better reflect
|
||||
the actual semantics (configurations are automatically migrated)
|
||||
|
||||
+24
-12
@@ -77,18 +77,26 @@ class TaskExecutor:
|
||||
"""
|
||||
return self.future.done()
|
||||
|
||||
def result(self, timeout: float | None = None) -> T:
|
||||
def result(self, timeout: float | None = None, cancel_on_timeout: bool = True) -> T:
|
||||
"""
|
||||
Blocks until the task is done or the timeout is reached, and returns the result.
|
||||
If an exception occurred during task execution, it is raised here.
|
||||
If the timeout is reached, a TimeoutError is raised (but the task is not cancelled).
|
||||
If the timeout is reached, a TimeoutError is raised.
|
||||
If the task is cancelled, a CancelledError is raised.
|
||||
|
||||
:param timeout: the maximum time to wait in seconds; if None, use the task's own timeout
|
||||
(which may be None to wait indefinitely)
|
||||
:param timeout: the maximum time to wait in seconds; if None, wait indefinitely
|
||||
:param cancel_on_timeout: whether to cancel the task if the timeout is reached.
|
||||
If the task has not yet started, cancellation prevents its execution entirely;
|
||||
if it is already running, the underlying thread continues to run, but its result
|
||||
is discarded and any waiter will receive a CancelledError.
|
||||
:return: the result of the task
|
||||
"""
|
||||
return self.future.result(timeout=timeout)
|
||||
try:
|
||||
return self.future.result(timeout=timeout)
|
||||
except concurrent.futures.TimeoutError:
|
||||
if cancel_on_timeout:
|
||||
self.cancel()
|
||||
raise
|
||||
|
||||
def cancel(self) -> None:
|
||||
"""
|
||||
@@ -98,18 +106,20 @@ class TaskExecutor:
|
||||
"""
|
||||
self.future.cancel()
|
||||
|
||||
def wait_until_done(self, timeout: float | None = None) -> None:
|
||||
def wait_until_done(self) -> bool:
|
||||
"""
|
||||
Waits until the task is done or the timeout is reached.
|
||||
Waits until the task is done or its timeout is reached.
|
||||
The task is done if it either completed successfully, failed with an exception, or was cancelled.
|
||||
|
||||
:param timeout: the maximum time to wait in seconds; if None, use the task's own timeout
|
||||
(which may be None to wait indefinitely)
|
||||
:return: True if the task is done (successfully, with failure, or via cancellation), False if the timeout was reached
|
||||
"""
|
||||
try:
|
||||
self.future.result(timeout=timeout)
|
||||
self.future.result(timeout=self.timeout)
|
||||
except concurrent.futures.TimeoutError:
|
||||
return False
|
||||
except:
|
||||
pass
|
||||
return True
|
||||
|
||||
def _process_task_queue(self) -> None:
|
||||
while True:
|
||||
@@ -130,7 +140,9 @@ class TaskExecutor:
|
||||
task.start()
|
||||
|
||||
# wait for task completion
|
||||
task.wait_until_done(timeout=task.timeout)
|
||||
is_done = task.wait_until_done()
|
||||
if not is_done:
|
||||
log.warning("Task %s did not complete within the timeout of %s seconds; continuing ...", task.name, task.timeout)
|
||||
with self._task_executor_lock:
|
||||
self._task_executor_current_task = None
|
||||
if task.logged:
|
||||
@@ -219,7 +231,7 @@ class TaskExecutor:
|
||||
:return: the result of the task execution
|
||||
"""
|
||||
task_obj = self.issue_task(task, name=name, logged=logged, timeout=timeout)
|
||||
return task_obj.result()
|
||||
return task_obj.result(timeout=timeout)
|
||||
|
||||
def get_last_executed_task(self) -> TaskInfo | None:
|
||||
"""
|
||||
|
||||
@@ -419,13 +419,19 @@ class Tool(Component):
|
||||
return result
|
||||
|
||||
# execute the tool in the agent's task executor, with timeout
|
||||
# (task timeout bounds task execution in the dispatcher once it runs, result timeout limits the time we wait)
|
||||
tool_call_error: ToolCallError
|
||||
timeout = self.agent.serena_config.tool_timeout
|
||||
try:
|
||||
task_exec = self.agent.issue_task(task, name=self.__class__.__name__)
|
||||
return task_exec.result(timeout=self.agent.serena_config.tool_timeout)
|
||||
task_exec = self.agent.issue_task(task, name=self.__class__.__name__, timeout=timeout)
|
||||
return task_exec.result(timeout=timeout)
|
||||
except ToolCallError as e:
|
||||
tool_call_error = e
|
||||
except Exception as e: # typically TimeoutError (other exceptions caught and forwarded as ToolCallError in the task)
|
||||
except TimeoutError:
|
||||
msg = f"Tool execution timed out after {timeout} seconds. "
|
||||
log.error(msg)
|
||||
tool_call_error = ToolCallError(msg)
|
||||
except Exception as e: # unexpected errors (exceptions in the task itself are caught and forwarded as ToolCallError)
|
||||
msg = f"{e.__class__.__name__}: {e}"
|
||||
log.error(msg)
|
||||
tool_call_error = ToolCallError(msg)
|
||||
|
||||
Reference in new issue
Block a user