-
-
Notifications
You must be signed in to change notification settings - Fork 328
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These need to return callbacks to stop the consumer/producer - this will work well with use_effect.
Unfortunately, async for msg in msgr.consume("my-message-type"): is fundamentally flawed since it relies on our async use_effect.
We would need a stateful async paradigm, similar to Django Channels consumers.
At some point we need to figure out async effects. I don't think that should be a blocker here.
Edit: see #1090
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dislike the start_consumer and start_producer from an "ease of learning" perspective. Following the ReactJS philsophy of diluting the core API to nothing but the most basic interface, we should simplify everything to just send and receive.
The "easiest to learn" way to advertise this functionality would be as such:
@component def demo_send_receive(): @use_messenger async def consumer(send, receive): while True: msg = await receive("my-message-type") await send("my-message-type", ...)
Just the bare concept of async cancellation, and lack of reliability for other hooks to work in a persistent way makes the user experience really awkward though.
For example, if we force the user to enable shield_cancel, we should consider whether state and set_state will always work as expected in the following scenario:
@component def demo_send_receive(): state, set_state = use_state(0) @use_messenger async def consumer(send, receive): while True: msg = await msgr.receive("my-message-type") print(state) set_state(msg + 1)
Or alternatively, if we do not enable shield_cancel is there potentially a gap in time where the previous use_effect has been de-registered, but the new one is not registered yet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would async with send, receive: not work here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can consider merging everything into a ReactPyContext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not have AsyncIterator be an accepted type on render, and then handle detection of this within layout.py?
Less APIs is generally more intuitive than more.
After today's tag up, two theoretical interfaces could look like this
Notes
- Use messenger will never restart a consumer that is already running
- We should timeout the consumer if it takes too long to teardown
- Async function interface needs a teardown parameter in the hook
Async Function Interface
from typing import Coroutine import asyncio from reactpy import use_state, component, use_effect def use_messenger(consumer: Coroutine, timeout: int = 10, teardown: Coroutine | None = None): ... @component def example(): state, set_state = use_state(0) async def consumer_teardown(send, receive): ... @use_messenger(timeout=20, teardown=consumer_teardown) async def consumer(receive, send, cancel: asyncio.Event): while True: msg = await receive("my-message-type") await send("my-message-type", ...) print(state) set_state(msg + 1) if cancel.is_set(): break
Class Based Interface
from typing import Coroutine import asyncio from reactpy import use_state, component, use_effect def use_messenger(consumer: Coroutine, timeout: int = 10): ... @component def example(): state, set_state = use_state(0) @use_messenger(timeout=20) class Consumer: async def __call__(self, receive, send, cancel: asyncio.Event): while True: msg = await receive("my-message-type") await send("my-message-type", ...) print(state) set_state(msg + 1) if cancel.is_set(): break async def teardown(self): ...
Archmonger
commented
Sep 16, 2023
Note: We may want to make some considerations around Django Channels Layers while developing this interface.
It's an existing ASGI messaging paradigm, so taking inspiration from their interface might not be a bad idea.
4402d31 to
1455f70
Compare
Uh oh!
There was an error while loading. Please reload this page.
By submitting this pull request you agree that all contributions to this project are made under the MIT license.
Issues
Currently, while the client and server have the ability to specify different message types, the server has no way of broadcasting messages other than layout events. Doing so is necessary for a number of different use cases...
Solution
Implement a generic
Messengerclass that allows for users to send/receive arbitrary messages to/from the client. Users will gain access to aMessengervia theuse_messengerhook. Usage could look like one of the following...Using
start_consumerandstart_producer:Using
sendandreceive:Ultimately, the
start_consumerandstart_producermethods are convenience methods that callsendandreceiveunder the hood.Checklist
changelog.rsthas been updated with any significant changes.