-
Notifications
You must be signed in to change notification settings - Fork 621
Introduce EventEmitter.Scope for event listeners
#3338
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
Conversation
|
@mikeshepherd @gvolpe I've published a snapshot if that's helpful for you: |
| _ <- readable.registerListener[F, Any]("close", dispatcher)(_ => channel.close.void) | ||
| _ <- readable.registerListener[F, js.Error]("error", dispatcher) { e => | ||
| error.complete(js.JavaScriptException(e)).void | ||
| scope <- facade.events.EventEmitter.openScope |
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 think this is going to change the ordering of unregistering the listeners wrt the finalizer for the thunk. The unregistering will now happen after rather than prior to that.
Notably this means that the registered listeners will get a close event that they wouldn't have previously.
This feels like a good thing? but I just wanted to call it out.
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.
Good call out. Yes, you're right. I wonder if it is worth doing the unregister in reverse order in EventEmitter.Scope 🤔
But yes, in general I think it's okay to continue receiving events, because the resource is already in the process of shutting down anyway, so I don't think it should be user-visible.
|
Hi @armanbilge, we've been running this change for a short while now, and it's had some positive effect, but not completely removed this class of error. We're now seeing it here instead: https://github.com/typelevel/fs2/blob/main/io/js/src/main/scala/fs2/io/ioplatform.scala#L237 but at a much lower rate. I'm happy to raise this up as a separate issue if you'd prefer. I'm also happy to have a go at solving it, but the solution is not so obvious to me here so I'd need some guidance. |
|
@mikeshepherd thanks for trying that and reporting the issue ... can you share any more details, like the stack trace possibly? Trying to understand when/where that |
|
Yeah absolutely. Apologies for the random quotes in it, it's just how it came, seemed more effort that it was worth to remove them 😀 I'm afraid I can't offer much more direction as to how and when this actually happens. I currently don't have enough detail in what's going on around that to tell you what the lambda is doing at that point. I can tell you that it's an occasional thing, we're consuming a kinesis stream so AWS retries on errors, and we get through the events eventually, just with a lot of retries caused by this |
|
Not at all, thanks, that's great! I will think about how the leak may be happening. |
Registering event listeners currently presents two conflicting challenges:
Registering a listener is resourceful i.e. it should eventually be unregistered.
Listeners must be registered without any risk of suspending to the event loop.
To be specific, something like
openConnection(...).flatTap(registerListener(_))is not safe in general, because the runtime may yield to the event loop between opening the connection and registering the listener, in which case an event may fire before the listener is installed (in the worst cases this leads to crashes due to unhandled error events).Two techniques to avoid suspending to the event loop are:
delay { val c = openConnection(...); c.registerListener(...); c }MicrotaskExecutorwhich does not yield to the global event loopIf both of these constraints are not considered when registering listeners we can encounter bugs such as #2663 and #3336.
This PR introduces a new concept
EventEmitter.Scope. This is a resourceful construct à laSupervisorthat may "supervise" a number of event listeners i.e. take responsibility for their cleanup, satisfying constraint (1). Furthermore, listeners may be registered with theScopeunsafely e.g. within adelayblock, satisfying constraint (2).NB "Scope" is a bit overloaded in FS2, but this is just internal private implementation so probably fine ... open to other names.
Closes #3336.