-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Coherent client/server way to work with "infinite" streams #1217
Comments
I replied in the discussion which should unblock you, but yield should have thrown if there is a write error. If you're not seeing that can you make a runnable reproducible example and share it? |
I created a repository with a modified eliza example: dionysiusmarquis/connect-es-issue-1217 (Branches for v1.4 and v2.0) I was actually wrong that the generator will never be rejected, but its still true, that it won't be rejected immidiately as soon as an abort signal triggers. So the difference between client and server is:
Example: In a system with server wide event promises that are resolved to send out data via rpc, client bound while loops will remain till the next event occures (which in worst case could be quite some time). |
Here an example that would work: async *infiniteStream(req: AccountRequest, context: HandlerContext) {
console.log(`Connected client with id ${req.id}.`);
const abortPromise = new Promise<void>((resolve) =>
context.signal.addEventListener("abort", () => {
resolve();
}),
);
try {
while (true) {
yield await Promise.race([
abortPromise,
delay(30000),
])
}
} catch(error) {
console.error((error as Error).message);
}
console.log(`Closed client connection with id ${req.id}.`);
}, Other clients can still register to wait for the event promise and the closed client won't leave a paused while loop till the next resolve. The client part was pretty intuitive, but the server part was harder to get to, when it comes to client close signals. |
Ah! I think I see what you want, you want the operation to be cancelled based on the signal. While the example succeeds in ending the request (with slight modification), it doesn't actually cancel the computation. Lets change your example to use fetch: async *infiniteStream(req: AccountRequest, context: HandlerContext) {
console.log(`Connected client with id ${req.id}.`);
const abortPromise = new Promise<void>((resolve, reject) =>
context.signal.addEventListener("abort", () => {
reject(); // You need to reject in order for `Promise.race` to throw.
}),
);
try {
while (true) {
yield await Promise.race([
abortPromise,
fetch('/some/url'),
])
}
} catch(error) {
console.error((error as Error).message);
}
console.log(`Closed client connection with id ${req.id}.`);
}, Now when the client aborts, yield will throw, but fetch will still happen. If we change the yield call to: while (true) {
yield await fetch('/some/url', { signal: context.signal } );
} Where the signal is directly passed to |
That would be a "promnise per client" approach. I guess fetch will eventually Given environment:
Why this single promise approach? This way there is no need to track any "listeners". The data provider resolves the promise and don't need to iterate through a pool of registered promises etc. If i'ts still unclear I'm can also expand the example repo with an example of this single promise approach |
Okay, now I understand. If there is one global promise, then using Closing this for now, feel free to reach out if you need anything. |
yep you're right Here is my current solution: #1216 |
Is your feature request related to a problem? Please describe.
The problen occures when establishing an "infinite" stream server side. I already outlined it here: #1216. There seems to be no way to break/reject the
async while yield
loop after the client closed the connection.Describe the solution you'd like
I think it would be coherent to handle it just like client side (following cancellation-and-timeouts#cancellation
This works fine client side. The
for await
will break Immediately once the abort signal is triggered:The server/sending side doesn't seem to reject the generator if the client closes the connection. Trying to follow the same pattern used client side:
Please specify whether the request is for Connect for Web or Connect for
Node.js.
Describe alternatives you've considered
I got arround it by writing the generator "by hand" (
{ next() ..., reject() ... }
) instead of usingwhile yield
, which seems a bit overkill.The text was updated successfully, but these errors were encountered: