221 lines
6.7 KiB
Python
221 lines
6.7 KiB
Python
"""Background task executor - Phase 10.4"""
|
|
|
|
import asyncio
|
|
from collections.abc import Callable, Coroutine
|
|
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from .manager import (
|
|
BackgroundTask,
|
|
BackgroundTaskManager,
|
|
BackgroundTaskStatus,
|
|
get_background_task_manager,
|
|
)
|
|
|
|
|
|
class BackgroundExecutor:
|
|
"""Executes background tasks with error handling and result storage.
|
|
|
|
Provides methods to execute tasks synchronously or asynchronously,
|
|
with full integration into BackgroundTaskManager for tracking.
|
|
"""
|
|
|
|
def __init__(self, task_manager: BackgroundTaskManager | None = None):
|
|
"""Initialize the executor.
|
|
|
|
Args:
|
|
task_manager: Optional BackgroundTaskManager instance.
|
|
If not provided, uses the global singleton.
|
|
"""
|
|
self._task_manager = task_manager or get_background_task_manager()
|
|
self._executors: dict[str, asyncio.Task] = {}
|
|
|
|
async def execute_task(
|
|
self,
|
|
task_id: str,
|
|
func: Callable[..., Coroutine[Any, Any, Any]],
|
|
*args: Any,
|
|
**kwargs: Any,
|
|
) -> BackgroundTask:
|
|
"""Execute a specific task by ID.
|
|
|
|
Args:
|
|
task_id: Unique task identifier
|
|
func: Async function to execute
|
|
*args: Positional arguments for the function
|
|
**kwargs: Keyword arguments for the function
|
|
|
|
Returns:
|
|
The BackgroundTask with result or error
|
|
"""
|
|
# Get or create task record
|
|
task = self._task_manager.get_task_status(task_id)
|
|
if task is None:
|
|
# Create a new task record if one doesn't exist
|
|
task = BackgroundTask(
|
|
id=task_id,
|
|
name=f"executor_task_{task_id}",
|
|
status=BackgroundTaskStatus.PENDING,
|
|
created_at=datetime.now(),
|
|
)
|
|
self._task_manager._tasks[task_id] = task
|
|
|
|
# Update status to running
|
|
task.status = BackgroundTaskStatus.RUNNING
|
|
task.started_at = datetime.now()
|
|
|
|
try:
|
|
# Execute the async function
|
|
result = await func(*args, **kwargs)
|
|
task.status = BackgroundTaskStatus.COMPLETED
|
|
task.result = result
|
|
except Exception as e:
|
|
task.status = BackgroundTaskStatus.FAILED
|
|
task.error = f"{type(e).__name__}: {str(e)}"
|
|
task.result = None
|
|
finally:
|
|
task.completed_at = datetime.now()
|
|
# Clean up executor reference
|
|
if task_id in self._executors:
|
|
del self._executors[task_id]
|
|
|
|
return task
|
|
|
|
async def execute_async(
|
|
self,
|
|
task_id: str,
|
|
func: Callable[..., Coroutine[Any, Any, Any]],
|
|
*args: Any,
|
|
**kwargs: Any,
|
|
) -> str:
|
|
"""Execute a task asynchronously in the background.
|
|
|
|
Args:
|
|
task_id: Unique task identifier
|
|
func: Async function to execute
|
|
*args: Positional arguments for the function
|
|
**kwargs: Keyword arguments for the function
|
|
|
|
Returns:
|
|
The task ID
|
|
"""
|
|
# Create task record if it doesn't exist
|
|
if self._task_manager.get_task_status(task_id) is None:
|
|
self._task_manager._tasks[task_id] = BackgroundTask(
|
|
id=task_id,
|
|
name=f"async_task_{task_id}",
|
|
status=BackgroundTaskStatus.PENDING,
|
|
created_at=datetime.now(),
|
|
)
|
|
|
|
# Create and store the asyncio task
|
|
async_task = asyncio.create_task(self.execute_task(task_id, func, *args, **kwargs))
|
|
self._executors[task_id] = async_task
|
|
|
|
return task_id
|
|
|
|
def cancel_task(self, task_id: str) -> bool:
|
|
"""Cancel a running task.
|
|
|
|
Args:
|
|
task_id: The task ID to cancel
|
|
|
|
Returns:
|
|
True if cancelled, False if not found or not running
|
|
"""
|
|
if task_id not in self._executors:
|
|
return False
|
|
|
|
self._executors[task_id].cancel()
|
|
del self._executors[task_id]
|
|
|
|
# Update task status
|
|
task = self._task_manager.get_task_status(task_id)
|
|
if task:
|
|
task.status = BackgroundTaskStatus.CANCELLED
|
|
task.completed_at = datetime.now()
|
|
return True
|
|
return False
|
|
|
|
def get_task_result(self, task_id: str) -> Any:
|
|
"""Get the result of a completed task.
|
|
|
|
Args:
|
|
task_id: The task ID
|
|
|
|
Returns:
|
|
The task result or None if not found/not completed
|
|
"""
|
|
task = self._task_manager.get_task_status(task_id)
|
|
if task and task.status == BackgroundTaskStatus.COMPLETED:
|
|
return task.result
|
|
return None
|
|
|
|
def get_task_error(self, task_id: str) -> str | None:
|
|
"""Get the error of a failed task.
|
|
|
|
Args:
|
|
task_id: The task ID
|
|
|
|
Returns:
|
|
The error message or None if not found/not failed
|
|
"""
|
|
task = self._task_manager.get_task_status(task_id)
|
|
if task and task.status == BackgroundTaskStatus.FAILED:
|
|
return task.error
|
|
return None
|
|
|
|
def is_task_running(self, task_id: str) -> bool:
|
|
"""Check if a task is currently running.
|
|
|
|
Args:
|
|
task_id: The task ID
|
|
|
|
Returns:
|
|
True if running, False otherwise
|
|
"""
|
|
return task_id in self._executors
|
|
|
|
def wait_for_task(self, task_id: str, timeout: float | None = None) -> BackgroundTask:
|
|
"""Wait for a task to complete.
|
|
|
|
Args:
|
|
task_id: The task ID to wait for
|
|
timeout: Optional timeout in seconds
|
|
|
|
Returns:
|
|
The completed BackgroundTask
|
|
|
|
Raises:
|
|
asyncio.TimeoutError: If task doesn't complete within timeout
|
|
asyncio.CancelledError: If task is cancelled
|
|
"""
|
|
if task_id not in self._executors:
|
|
task = self._task_manager.get_task_status(task_id)
|
|
if task:
|
|
return task
|
|
raise ValueError(f"Task {task_id} not found")
|
|
|
|
async def wait_task() -> BackgroundTask:
|
|
await self._executors[task_id]
|
|
return self._task_manager.get_task_status(task_id)
|
|
|
|
return asyncio.run_until_complete(asyncio.wait_for(wait_task(), timeout=timeout))
|
|
|
|
@property
|
|
def task_manager(self) -> BackgroundTaskManager:
|
|
"""Get the underlying task manager."""
|
|
return self._task_manager
|
|
|
|
|
|
# Global executor instance
|
|
_executor: BackgroundExecutor | None = None
|
|
|
|
|
|
def get_background_executor() -> BackgroundExecutor:
|
|
"""Get the global BackgroundExecutor instance."""
|
|
global _executor
|
|
if _executor is None:
|
|
_executor = BackgroundExecutor()
|
|
return _executor
|