Skip to content

Discussion: async #11

@roee30

Description

@roee30

Out of curiosity, I tried incorporating coroutine handling into Runtime so that async code could be run, basically implementing an Async ability. This is a very partial POC.

patch for runtime.py:

2c2,4
< 
---
> import types
> import asyncio
> import threading
35c37,39
< 
---
>         object.__setattr__(self, "loop", asyncio.new_event_loop())
>         self.loop._thread_id = threading.get_ident()
>         asyncio.events._set_running_loop(self.loop)
134a135
>             from inspect import isawaitable
139a141,148
>                         case co if isawaitable(co):
>                             if isinstance(co, types.CoroutineType):
>                                 task = self.loop.create_task(co)
>                             else:
>                                 task = co
>                             while not task.done():
>                                 self.loop._run_once()
>                             effect.send(task.result())

Makes this work:

import asyncio
from dataclasses import dataclass
from typing import Never, Awaitable, Iterable

import httpx
from stateless import Effect, Runtime, Depend, depend, catch, throws

SYNC = False

async def gather[R](cos: Iterable[Awaitable[R]]) -> list[R]:
    if SYNC:
        return [await c for c in cos]
    return await asyncio.gather(*cos)


@dataclass(frozen=True)
class HTTP:
    client: httpx.AsyncClient

    def get(self, url, *args, **kwargs) -> Awaitable[httpx.Response]:
        return self.client.get(url, *args, **kwargs)


def http_client() -> Depend[Never, HTTP]:
    client = yield httpx.AsyncClient().__aenter__()
    return HTTP(client)


def get_concurrently():
    http = yield from depend(HTTP)
    result = yield gather(http.get(f"https://{x}.com") for x in ['google', 'reddit', 'xkcd'])
    return result


runtime = Runtime().use_effect(http_client())
e = get_concurrently()
runtime.run(e)

Changing SYNC to True doubles the runtime, which verifies that the coroutines originally run concurrently.

Problems:

  • doesn't propagate errors from HTTP.get() to the caller
  • yielding coroutines, which are not abilities, mess with static typing
  • HTTP.get() invocations can't be encapsulated in effects (functions) because gather() can't deal with plain generators. Solving this probably requires implementing a special flavor of gather() that drives each generator until it yields a coroutine, collects them, and calls gather(), repeating until all generators are exhausted

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions