-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add alternative since StreamController.broadcast({sync:true}) is not reentrant #22240
Comments
If you comment out the second listener line, the result is instead out of order callbacks, with no exception: test1 |
cc @lrhn. |
Removed Priority-Unassigned label. |
According to floitsch and lrn in issue #21076, it's invalid to use synchronous streams/futures for anything other than forwarding already-asynchronous events. I would also prefer that this be allowed, but it sounds like it's not in the cards. |
Why not? |
You'd have to ask them, I'm afraid. |
That's what I was doing. :-) |
Synchronous stream controllers are not reentrant - you may not call The reason is simple: It is not possible to both satisfy the requirement that events are delivered in the correct order, and that they are delivered synchronously (before the "add" call returns), if you can fire new events during an event callback. Example: A controller has two listeners, A and B. An event is added, and immediately it is delivered to first A and then B. However, in A's event callback it now adds another event. The new event should be delivered immediately to both A and B. If it is delivered synchronously, then B receives the second event before the first. If not, well, then it's not delivered synchronously. So, to prevent incorrect event ordering, adding an event during another event's dispatch is an error, and the controller throws if you try. It doesn't throw if there is only one listener, and that's probably a bug (or really: a misguided attempt to be lenient, since it can't get out-of-order delivery with only one listener, but it's still bad and breaks when you get a second listener). Why it gets the "test5" event after printing the "test8" is really weird, and I'll investigate. We could probably do some workaround where the first event for B is delivered first, during the second call to "add" (effectively firing all pending events on any event being added, before firing the new one), but that very quickly gets very complicated (it also has to handle subscriptions being added and only later events being delivered to them, and listeners being cancelled). Since the purpose of a synchronous stream controller is only to allow a slightly more efficient way to implement event forwarding, it is only intended for adding events as the last action of handling another event. Adding a complicated event buffering system in order to make it reentrant was not considered worth the effort, and would just add overhead to what is intended as a pure performance optimization (and used very carefully). The implementation of a synchronous stream controller is actually pretty simple, much simpler than the normal controller, because it doesn't have to do event buffering. If I were to design the library now, I would probably make the synchronous controller a separate class, so that it could be documented independently. A simple "sync" parameter doesn't really show how big a difference there is between normal and synchronous controllers. In your case, I recommend just using a normal controller. It is never an error to use a normal controller, but using a synchronous controller incorrectly can lead to a stream implementation that breaks the Stream contract, which may cause some listeners to crash an burn. You have to be very careful when you add events to a synchronous stream. For example, never add events in the onListen callback! The error is reported late because it happens in an event callback, and because you are using a sync controller in a non-tail position. Each event callback assumes that it runs from top-level event loop or microtask loop. Any uncaught errors in the callback are reported as uncaught at top leve. When calling a callback with a sync controller, it pretends that the synchronous call to the callback happens at top level, and the uncaught error thrown by calling "add" reentrantly is caught, and scheduled for the next microtask. Then the "add" returns, and because it keeps doing stuff synchronously, you get more prints before the error is reported. That's one of the reasons why you should only add events to a sync controller as the very last action of another event, because what comes after that "add" will be executed before any errors in the callbacks are reported. Added Accepted label. |
The semantic I'm looking for is the following: • When an event is dispatched, all the handlers are called before the dispatch returns Are these requirements requirements that can be met by the Stream API in some way? It would be unfortunate to have to make a new API, given how close the Stream API appears at first glance to be to these semantics. |
That semantics is definitely not going to work with streams. |
Florian suggested a "Dispatcher" API that had similar methods as Stream but with the semantics from comment 9, which would solve this problem for me perfectly. (Should we file a bug for that?) There should probably also be a bug for the difference in behaviour between one listener and two (see comment 1 vs the original comment here). |
The one/two listener discrepancy has been fixed. It now throws all the time. I fixed a bug in take/skip at the same time (they are not widely used on broadcast streams for good reason). For the "dispatcher" (do not like name!) class it should be possible to do something that's efficient and simple. |
Change to be an enhancement request. The Stream behavior is not going to change. Removed Type-Defect label. |
Is this implemented or will it ever be? |
The text was updated successfully, but these errors were encountered: