Skip to content

academy.executor

EventLoopExecutor

EventLoopExecutor(factory: ExchangeFactory[Any])

Bases: Executor

Modified Executor that packs multiple agents into one event loop.

This executor spends a single inner worker on a hidden host agent, then runs every subsequent Agent submission onto that host's own event loop.

Parameters:

Source code in academy/executor.py
def __init__(
    self,
    factory: ae.ExchangeFactory[Any],
):
    self._loop: asyncio.AbstractEventLoop | None = None
    self._loop_ready: threading.Event = threading.Event()
    self._factory = factory
    self._client: ae.UserExchangeClient[Any] | None = None
    self._host: Handle[Any] | None = None
    self._shutdown = False
    self._host_lock: asyncio.Lock = asyncio.Lock()
    self._pending_futures: set[Future[Any]] = set()
    self._host_task: asyncio.Task[None] | None = None

    self._thread: threading.Thread = Thread(target=self._thread_main)
    self._thread.start()
    self._loop_ready.wait()

submit

submit(
    fn: Callable[..., Any], /, *args: Any, **kwargs: Any
) -> Future[Any]

Run a callable on the host agent's event loop.

Parameters:

  • fn (Callable[..., Any]) –

    Callable to run on the host.

  • *args (Any, default: () ) –

    Positional arguments for submitted function.

  • **kwargs (Any, default: {} ) –

    Keyword arguments for submitted function.

Returns: Future resolving when fn finishes on host.

Source code in academy/executor.py
def submit(
    self,
    fn: Callable[..., Any],
    /,
    *args: Any,
    **kwargs: Any,
) -> Future[Any]:
    """Run a callable on the host agent's event loop.

    Args:
        fn: Callable to run on the host.
        *args: Positional arguments for submitted function.
        **kwargs: Keyword arguments for submitted function.

    Returns: Future resolving when fn finishes on host.
    """
    if self._shutdown:
        raise RuntimeError('Cannot submit after host shutdown')

    if fn is _run_agent_on_worker:
        spec = args[0]
        fn, args, kwargs = _run_agent_async, (spec,), {}

    else:
        raise ValueError(
            'Only functions of the type _run_agent_on_worker '
            'are allowed to be submitted.',
        )

    assert self._loop is not None
    task_future = asyncio.run_coroutine_threadsafe(
        self._submit_async(fn, args, kwargs),
        self._loop,
    )
    self._pending_futures.add(task_future)
    task_future.add_done_callback(self._pending_futures.discard)

    return task_future

shutdown

shutdown(
    wait: bool = True, cancel_futures: bool = True
) -> None

Shut down the owned thread and the host agent.

Args: wait: Wait for the inner executor to finish before returning. cancel_futures: Cancel pending futures before shutting down.

Source code in academy/executor.py
def shutdown(
    self,
    wait: bool = True,
    cancel_futures: bool = True,
) -> None:
    """Shut down the owned thread and the host agent.

    Args:
    wait: Wait for the inner executor to finish before returning.
    cancel_futures: Cancel pending futures before shutting down.
    """
    self._shutdown = True
    assert self._loop is not None

    if cancel_futures:
        for future in list(self._pending_futures):
            future.cancel()

    if self._host is not None:
        future_host = asyncio.run_coroutine_threadsafe(
            self._host.shutdown(),
            self._loop,
        )
        future_host.result()

    if self._client is not None:
        future_client = asyncio.run_coroutine_threadsafe(
            self._client.close(),
            self._loop,
        )
        future_client.result()

    self._loop.call_soon_threadsafe(self._loop.stop)

    if wait:
        self._thread.join()