-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
feat(context): introduce context events and observers for bind/unbind #2291
Conversation
c02bb89
to
2baebe8
Compare
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.
Thank you @raymondfeng for extracting this smaller patch, is so much easier to review! Thanks to the smaller size, I was able to spot few areas that deserve further discussion, PTAL below.
@jannyHou @hacksparrow I'd like you to thoroughly review this pull request too, especially the high-level architecture.
My first and my last comments are related, the order in which notifications are processed depends on the way how we schedule queue-processing callbacks. Besides the impact on the behavior observed by Context consumers, we should also consider performance implications. Scheduling a new |
The interface looks good. However, as pointed out by Miroslav, notification processing needs some more work, maybe resulting in additions to to the API. Let's the keep the discussion going. |
We have the following implementation:
Even though the function for nextTick is async, errors thrown from listeners are caught and logged. There is no chance for unhandled errors to happen. IMO, it's a simple fire-and-forget implementation. |
2baebe8
to
97316a6
Compare
@bajtos @hacksparrow Thank you for the feedback. I now use https://github.com/jessetane/queue to handle event notifications. PTAL. |
7ebd4b5
to
43f21c0
Compare
Good update @raymondfeng. Some more feedback. The Test cases should be also added to document how errors are handled in sync and sync We should be able to tell where in the context chain, an error originated from. |
} from '../..'; | ||
|
||
import {promisify} from 'util'; | ||
const setImmediatePromise = promisify(setImmediate); |
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.
Let's use Bluebird's convention of adding Async
postfix to promise-enabled functions.
const setImmediatePromise = promisify(setImmediate); | |
const setImmediateAsync = promisify(setImmediate); |
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 copied it from https://nodejs.org/api/timers.html#timers_setimmediate_callback_args but I'm open to any convention.
FWIW, I see at least two potential places that can trigger unhandled error:
|
What were the criteria on which you chose this particular module? I did a quick search on npm (https://www.npmjs.com/search?q=queue) and p-queue seems to be more popular (p-queue: 283k downloads/month vs queue: 57k downloads/months) and also has more ergonomic API based on promises, not events.
The downside I see is that Setting the choice of a queue library aside. It's not clear to me what are the benefits of adding a queue library, when we are still calling |
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.
Few more comments specific to individual lines of code.
Uh oh, I am not a fan of making the filter async. I am concerned it can complicate the code too much. Remember, we are not implementing a generic observer/listener API here, but a specific feature to allow LB4 components to be notified when particular bindings are added or removed. A typical use case: Code wishing to filter bindings asynchronously should implement filtering inside
FWIW, I was proposing to remove |
@raymondfeng please take a look at the following use-case/scenario, I believe it's not covered by your patch yet:
|
Hmm, I like this approach. @raymondfeng what do you say? |
The |
It is intentional to have |
I only found
I'm adding the queue based on your comments ( |
The key challenge is two-fold:
The proposed design in this PR is to use a background task queue to perform notifications in ticks after the binding is added/removed from the context via sync apis. |
f6c793e
to
2b07610
Compare
I wonder if we can expose
The other option is to have |
+💯. |
41c45b4
to
fd3464d
Compare
Now it happens within Promise microtask queue as implemented by |
aa9c9fc
to
1b41d1d
Compare
@bajtos PTAL |
1b41d1d
to
9309d59
Compare
packages/context/src/context.ts
Outdated
// current context observers. | ||
this.emit(notificationEvent, { | ||
binding, | ||
observers: new Set(this.observers), |
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.
In your implementation, you are trading off performance of event notification for performance of adding a new observer:
- when a new observer is added, we preserve the Set instance in
this.observers
- when a new event is fired, a new Set instance is created
I expect that events will be triggered more often than new observers are added, therefore we should do a different trade-off:
- when a new observer is added, a new Set instance is created, it replaces the value stored in
this.observers
- when a new event is fired, we take a reference to the Set instance in
this.observers
because this instance is considered as immutable
Feel free to fix this in a follow-up pull request if you prefer.
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.
A new notification event is only fired when there are observers. I'll defer this micro-optimization.
A general observation: the file Let's leave such refactoring out of scope of this pull request though, we should focus on getting the initial implementation finished and merged now. |
9309d59
to
ab00a5e
Compare
@bajtos PTAL |
9425ee6
to
9cb8a4f
Compare
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.
Looks mostly good.
I'd like to @jannyHou and @hacksparrow to review these changes before approving the patch.
ffda885
to
fb6c24a
Compare
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 am able to understand the usage of subscribing multiple observers for a given context. And how the sync/async observe function are handled differently.
It would cost me more time to review the very detail of mechanism of notification, and to be realistic, I would like to see this PR merged to unblock the authentication extension story. And we always have a chance to improve.
I have a design related question: why do we introduce a Subscription
interface to handle the unsubscribe
action? I also added a short comment in the corresponding test case. People can unsubscribe an observer by calling either ctx.unsubscribe()
or subscription.unsubscribe()
, what would be the difference?
it('allows subscription.unsubscribe()', () => { | ||
const subscription = ctx.subscribe(nonMatchingObserver); | ||
expect(ctx.isSubscribed(nonMatchingObserver)).to.true(); | ||
subscription.unsubscribe(); |
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 having two ways to unsubscribe an observer?
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.
For subscription.subscribe()
, we don't need to remember the observer. It follows https://github.com/tc39/proposal-observable
* these listeners when this context is closed. | ||
*/ | ||
protected _parentEventListeners: | ||
| Map< |
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.
an extra |
?
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.
That's added by prettier
.
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.
LGTM.
packages/context/src/context.ts
Outdated
* @param err Error | ||
*/ | ||
private handleNotificationError(err: unknown) { | ||
this._debug(err); |
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 debug statement will be confusing to our users - it prints the error only but does not give any context where is the error coming from (a context-event listener).
I am proposing to remove this _debug
statement and include the error in the debug statements on lines 215 and 220 below.
fb6c24a
to
dade36a
Compare
dade36a
to
d43b75b
Compare
Spin-off from #2122
Followed-up PRs:
#2122 (depends on #2291)
#2249
#2259
Checklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated