-
Notifications
You must be signed in to change notification settings - Fork 51
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
refactor: Replace subscription events publisher #2686
Changes from 4 commits
383b20a
9f62ba7
0f3e2fd
c433a28
ca612ce
2e7d81b
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 |
---|---|---|
|
@@ -19,83 +19,75 @@ | |
"github.com/sourcenetwork/defradb/internal/planner" | ||
) | ||
|
||
func (db *db) checkForClientSubscriptions(r *request.Request) ( | ||
*events.Publisher[events.Update], | ||
*request.ObjectSubscription, | ||
error, | ||
) { | ||
// handleSubscription checks for a subscription within the given request and | ||
// starts a new go routine that will return all subscription results on the returned | ||
// channel. If a subscription does not exist on the given request nil will be returned. | ||
func (db *db) handleSubscription(ctx context.Context, r *request.Request) (<-chan client.GQLResult, error) { | ||
if len(r.Subscription) == 0 || len(r.Subscription[0].Selections) == 0 { | ||
// This is not a subscription request and we have nothing to do here | ||
return nil, nil, nil | ||
return nil, nil // This is not a subscription request and we have nothing to do here | ||
} | ||
|
||
if !db.events.Updates.HasValue() { | ||
return nil, nil, ErrSubscriptionsNotAllowed | ||
return nil, ErrSubscriptionsNotAllowed | ||
} | ||
|
||
s := r.Subscription[0].Selections[0] | ||
if subRequest, ok := s.(*request.ObjectSubscription); ok { | ||
pub, err := events.NewPublisher(db.events.Updates.Value(), 5) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
return pub, subRequest, nil | ||
selections := r.Subscription[0].Selections[0] | ||
subRequest, ok := selections.(*request.ObjectSubscription) | ||
if !ok { | ||
return nil, client.NewErrUnexpectedType[request.ObjectSubscription]("SubscriptionSelection", selections) | ||
} | ||
// unsubscribing from this publisher will cause a race condition | ||
// https://github.com/sourcenetwork/defradb/issues/2687 | ||
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. question: Is this a new issue, or was the old code affected to? And where is the race condition? Is it a test artefact, or production code? 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 believe this is an old issue. The previous code never called |
||
pub, err := events.NewPublisher(db.events.Updates.Value(), 5) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return nil, nil, client.NewErrUnexpectedType[request.ObjectSubscription]("SubscriptionSelection", s) | ||
} | ||
resCh := make(chan client.GQLResult) | ||
go func() { | ||
defer close(resCh) | ||
|
||
func (db *db) handleSubscription( | ||
ctx context.Context, | ||
pub *events.Publisher[events.Update], | ||
r *request.ObjectSubscription, | ||
) { | ||
for evt := range pub.Event() { | ||
txn, err := db.NewTxn(ctx, false) | ||
if err != nil { | ||
log.ErrorContext(ctx, err.Error()) | ||
continue | ||
} | ||
// listen for events and send to the result channel | ||
for { | ||
var evt events.Update | ||
select { | ||
case val := <-pub.Event(): | ||
evt = val | ||
case <-ctx.Done(): | ||
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. todo: By not looping through the Can you please confirm the behaviour, and make any necessary adjustments please? (note: IIRC testing of subscriptions is not as comprehensive as most of our other queries, and it uses an old framework) 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. Good catch! I've updated it to handle the channel closing. |
||
return // context cancelled | ||
} | ||
|
||
ctx := SetContextTxn(ctx, txn) | ||
db.handleEvent(ctx, pub, evt, r) | ||
txn.Discard(ctx) | ||
} | ||
} | ||
txn, err := db.NewTxn(ctx, false) | ||
if err != nil { | ||
log.ErrorContext(ctx, err.Error()) | ||
continue | ||
} | ||
|
||
func (db *db) handleEvent( | ||
ctx context.Context, | ||
pub *events.Publisher[events.Update], | ||
evt events.Update, | ||
r *request.ObjectSubscription, | ||
) { | ||
txn := mustGetContextTxn(ctx) | ||
identity := GetContextIdentity(ctx) | ||
p := planner.New( | ||
ctx, | ||
identity, | ||
db.acp, | ||
db, | ||
txn, | ||
) | ||
ctx := SetContextTxn(ctx, txn) | ||
identity := GetContextIdentity(ctx) | ||
|
||
s := r.ToSelect(evt.DocID, evt.Cid.String()) | ||
p := planner.New(ctx, identity, db.acp, db, txn) | ||
s := subRequest.ToSelect(evt.DocID, evt.Cid.String()) | ||
|
||
result, err := p.RunSubscriptionRequest(ctx, s) | ||
if err != nil { | ||
pub.Publish(client.GQLResult{ | ||
Errors: []error{err}, | ||
}) | ||
return | ||
} | ||
result, err := p.RunSubscriptionRequest(ctx, s) | ||
if err == nil && len(result) == 0 { | ||
txn.Discard(ctx) | ||
continue // Don't send anything back to the client if the request yields an empty dataset. | ||
} | ||
res := client.GQLResult{ | ||
Data: result, | ||
} | ||
if err != nil { | ||
res.Errors = []error{err} | ||
} | ||
|
||
// Don't send anything back to the client if the request yields an empty dataset. | ||
if len(result) == 0 { | ||
return | ||
} | ||
select { | ||
case resCh <- res: | ||
txn.Discard(ctx) | ||
case <-ctx.Done(): | ||
txn.Discard(ctx) | ||
return // context cancelled | ||
} | ||
} | ||
}() | ||
|
||
pub.Publish(client.GQLResult{ | ||
Data: result, | ||
}) | ||
return resCh, nil | ||
} |
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.
discussion: I think you are removing functionality here - IIRC the events package stuff allows for multiple concurrent readers of the stream. The simple Go chan that you are replacing it lacks this functionality and will mean any concurrent readers will be competing with each other for each item.
The CLI/http clients are unaffected, but the embedded Go client is.
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.
It makes more sense to me that we assume one reader per request by default. The consumer of the channel then has the option to decide if they want to create an
events.Publisher
and publish the channel results to it.