-
Notifications
You must be signed in to change notification settings - Fork 162
feat: add subscriptions hook #1243
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
Changes from 12 commits
38f8826
824362b
cdfa9bf
f8de362
e500d1c
c970c07
2a6b5f0
1e6ed5f
d776943
2f34327
dc89310
3cd5690
bfad223
91dbadd
207adf7
3257c52
ec79b06
e74d079
41b92a0
7bb7431
e0845be
3b61ef2
ae55c92
87cb3a8
ea8ab75
2ed6134
5fa5a76
8731ff2
6793f09
fdf829a
7b569b6
4a94540
a673838
46d97b2
447c103
7bb99a5
f8c9d06
baec1da
2100f14
311b021
e0af0a0
f4eba63
0efce3c
6234791
8df1b67
a0024f0
ddc652f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,3 +26,16 @@ type AsyncSubscriptionDataSource interface { | |
| AsyncStop(id uint64) | ||
| UniqueRequestID(ctx *Context, input []byte, xxh *xxhash.Digest) (err error) | ||
| } | ||
|
|
||
| // SubscriptionDataSourceHookable is a hookable interface for subscription data sources. | ||
| // It is used to call a function when a subscription is started. | ||
| // This is useful for data sources that need to do some work when a subscription is started, | ||
| // e.g. to establish a connection to the data source or to emit updates to the client. | ||
| // The function is called with the context and the input of the subscription. | ||
| // The function is called before the subscription is started and can be used to emit updates to the client. | ||
| type SubscriptionDataSourceHookable interface { | ||
| // OnSubscriptionStart is called when a new subscription is created | ||
| // If close is true, the subscription is closed. | ||
| // If an error is returned, the error is propagated to the client. | ||
| OnSubscriptionStart(ctx *Context, input []byte) (close bool, err error) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This interface doesn't reflect the ADR.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I fixed the name to be better aligned with the interface described in the ADR. Still it will not be like what the ADR described, because this is the interface on a datasource, but the ADR describe the custom module interface that is in the router (wundergraph/cosmo#2059, still working on it) |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -569,6 +569,16 @@ func (r *Resolver) handleTriggerComplete(triggerID uint64) { | |
| r.completeTrigger(triggerID) | ||
| } | ||
|
|
||
| // callSubscriptionDataSourceStartHook is used to call the OnSubscriptionStart method of the subscription data source | ||
| // if the subscription data source implements the SubscriptionDataSourceHook interface. | ||
| // This is used to allow external code to emit updates on this subscription. | ||
| func callSubscriptionDataSourceStartHook(ctx *Context, source SubscriptionDataSource, input []byte) (close bool, err error) { | ||
| if hook, ok := source.(SubscriptionDataSourceHookable); ok { | ||
| return hook.OnSubscriptionStart(ctx, input) | ||
| } | ||
| return false, nil | ||
| } | ||
|
|
||
| func (r *Resolver) handleAddSubscription(triggerID uint64, add *addSubscription) { | ||
| var ( | ||
| err error | ||
|
|
@@ -590,6 +600,12 @@ func (r *Resolver) handleAddSubscription(triggerID uint64, add *addSubscription) | |
| s.heartbeat = true | ||
| } | ||
|
|
||
| // Set the subscription updater to the resolver to allow external code to emit updates | ||
| // on this subscription | ||
| add.ctx.SetSubscriptionUpdater(func(data []byte) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't look right. Events must be emitted through the events channel. This can cause cause issues because the writer is not synchronised.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, so now this has been changed. I'm running the hooks inside the worker to avoid slowing the main loop to much. The updates are then sent directly using the executeSubscriptionUpdate, without using the events (that would have been complex, because events are going to be sent to all the subscriptions of a trigger, but I want them to be sent only to the current subscription) or even the workChan, to avoid to block the subscription worker if a modules return more messages than the buffer size. |
||
| r.handleTriggerUpdateSubscription(add.ctx, s, data) | ||
| }) | ||
|
|
||
| // Start the dedicated worker goroutine where the subscription updates are processed | ||
| // and writes are written to the client in a single threaded manner | ||
| go s.startWorker() | ||
|
|
@@ -603,6 +619,13 @@ func (r *Resolver) handleAddSubscription(triggerID uint64, add *addSubscription) | |
| if r.options.Debug { | ||
| fmt.Printf("resolver:trigger:subscription:added:%d:%d\n", triggerID, add.id.SubscriptionID) | ||
| } | ||
| close, errStartHook := callSubscriptionDataSourceStartHook(add.ctx, add.resolve.Trigger.Source, add.input) | ||
| if errStartHook != nil { | ||
| r.asyncErrorWriter.WriteError(add.ctx, errStartHook, add.resolve.Response, add.writer) | ||
| } | ||
| if close { | ||
| r.closeTrigger(triggerID, SubscriptionCloseKindNormal) | ||
| } | ||
| return | ||
| } | ||
|
|
||
|
|
@@ -639,6 +662,15 @@ func (r *Resolver) handleAddSubscription(triggerID uint64, add *addSubscription) | |
| asyncDataSource = async | ||
| } | ||
|
|
||
| close, errStartHook := callSubscriptionDataSourceStartHook(add.ctx, add.resolve.Trigger.Source, add.input) | ||
| if errStartHook != nil { | ||
| r.asyncErrorWriter.WriteError(add.ctx, errStartHook, add.resolve.Response, add.writer) | ||
| } | ||
| if close { | ||
| r.closeTrigger(triggerID, SubscriptionCloseKindNormal) | ||
| return | ||
| } | ||
|
|
||
| go func() { | ||
| if r.options.Debug { | ||
| fmt.Printf("resolver:trigger:start:%d\n", triggerID) | ||
|
|
@@ -757,6 +789,34 @@ func (r *Resolver) handleRemoveClient(id int64) { | |
| } | ||
| } | ||
|
|
||
| func (r *Resolver) handleTriggerUpdateSubscription(ctx *Context, sub *sub, data []byte) { | ||
| if err := ctx.ctx.Err(); err != nil { | ||
| return // no need to schedule an event update when the client already disconnected | ||
| } | ||
| skip, err := sub.resolve.Filter.SkipEvent(ctx, data, r.triggerUpdateBuf) | ||
| if err != nil { | ||
| r.asyncErrorWriter.WriteError(ctx, err, sub.resolve.Response, sub.writer) | ||
| return | ||
| } | ||
| if skip { | ||
| return | ||
| } | ||
|
|
||
| fn := func() { | ||
| r.executeSubscriptionUpdate(ctx, sub, data) | ||
| } | ||
|
|
||
| select { | ||
| case <-r.ctx.Done(): | ||
| // Skip sending all events if the resolver is shutting down | ||
| return | ||
| case <-ctx.ctx.Done(): | ||
| // Skip sending the event if the client disconnected | ||
| case sub.workChan <- workItem{fn, false}: | ||
| // Send the event to the subscription worker | ||
| } | ||
| } | ||
|
|
||
| func (r *Resolver) handleTriggerUpdate(id uint64, data []byte) { | ||
| trig, ok := r.triggers[id] | ||
| if !ok { | ||
|
|
@@ -767,32 +827,7 @@ func (r *Resolver) handleTriggerUpdate(id uint64, data []byte) { | |
| } | ||
|
|
||
| for c, s := range trig.subscriptions { | ||
|
alepane21 marked this conversation as resolved.
|
||
| c, s := c, s | ||
| if err := c.ctx.Err(); err != nil { | ||
| continue // no need to schedule an event update when the client already disconnected | ||
| } | ||
| skip, err := s.resolve.Filter.SkipEvent(c, data, r.triggerUpdateBuf) | ||
| if err != nil { | ||
| r.asyncErrorWriter.WriteError(c, err, s.resolve.Response, s.writer) | ||
| continue | ||
| } | ||
| if skip { | ||
| continue | ||
| } | ||
|
|
||
| fn := func() { | ||
| r.executeSubscriptionUpdate(c, s, data) | ||
| } | ||
|
|
||
| select { | ||
| case <-r.ctx.Done(): | ||
| // Skip sending all events if the resolver is shutting down | ||
| return | ||
| case <-c.ctx.Done(): | ||
| // Skip sending the event if the client disconnected | ||
| case s.workChan <- workItem{fn, false}: | ||
| // Send the event to the subscription worker | ||
| } | ||
| r.handleTriggerUpdateSubscription(c, s, data) | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.