Repository navigation
Buffer subscription changes when in repo mode - #1052
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1052 +/- ##
==========================================
+ Coverage 72.83% 72.85% +0.01%
==========================================
Files 391 391
Lines 21745 21793 +48
Branches 2826 2836 +10
==========================================
+ Hits 15838 15877 +39
- Misses 4785 4787 +2
- Partials 1122 1129 +7 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
pmachapman
left a comment
There was a problem hiding this comment.
@pmachapman reviewed 4 files and all commit messages, and made 3 comments.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on ddaspit and Enkidu93).
src/DataAccess/src/SIL.DataAccess/MemoryRepository.cs line 360 at r1 (raw file):
if (kvp.Value(entity)) allSubscriptions.Add(kvp.Key); }
NIT: You could combine these two conditions
Code quote:
if (kvp.Key.Mode == SubscriptionMode.Repository)
{
if (kvp.Value(entity))
allSubscriptions.Add(kvp.Key);
}
else if (kvp.Key.Change.Entity is null)
{
if (kvp.Value(entity))
allSubscriptions.Add(kvp.Key);
}src/DataAccess/src/SIL.DataAccess/MemorySubscription.cs line 23 at r1 (raw file):
{ await TaskTimeout(_changeEvent.WaitAsync, timeout ?? Timeout.InfiniteTimeSpan, cancellationToken) .ConfigureAwait(false);
NIT: You could remove this before the if statement then remove the else block.
Code quote:
await TaskTimeout(_changeEvent.WaitAsync, timeout ?? Timeout.InfiniteTimeSpan, cancellationToken)
.ConfigureAwait(false);src/DataAccess/src/SIL.DataAccess/MongoSubscription.cs line 15 at r1 (raw file):
private BsonDocument? _resumeToken; private readonly SubscriptionMode _mode; private readonly Queue<EntityChange<T>> _changes;
You use a ConcurrentQueue in the MemorySubscription, but just a normal Queue here. I would have thought both classes would have the same thread safety requirements?
Code quote:
private readonly Queue<EntityChange<T>> _changes;| { | ||
| await TaskTimeout(_changeEvent.WaitAsync, timeout ?? Timeout.InfiniteTimeSpan, cancellationToken) | ||
| .ConfigureAwait(false); | ||
| if (Mode == SubscriptionMode.Repository) |
There was a problem hiding this comment.
I think the same change can be returned twice. If HandleChange enqueues a change while the waiter is checking _changes.IsEmpty, both of them set the event. The next wait then returns with an empty queue, and Change still holds the previous change. Would it be possible to loop on the wait until a change is actually dequeued?
There was a problem hiding this comment.
Good catch. I think this fixes it. Let me know if I'm not following.
There was a problem hiding this comment.
The loop looks good. Timeout.InfiniteTimeSpan is -1 ms, so remainingTimeout is always negative when no timeout is passed. This causes WaitForChangeAsync() to return immediately without waiting. It affects callers like GetNextFinishedBuild and LocalBuildJobRunner. Would it be possible to only compute the remaining time when a timeout is specified?
| foreach (KeyValuePair<MemorySubscription<T>, Func<T, bool>> kvp in _subscriptions) | ||
| { | ||
| if (kvp.Key.Change.Entity is null) | ||
| if (kvp.Key.Mode == SubscriptionMode.Repository) |
There was a problem hiding this comment.
This sends deletes to repository mode subscriptions. MongoSubscription only watches inserts, updates and replaces in repository mode, so tests would see events that never happen in production. Can you skip deletes for repository mode subscriptions here?
There was a problem hiding this comment.
That makes sense. Done.
There was a problem hiding this comment.
This checks the type of the last change the subscription received, not the type of the change being sent, so deletes are still sent. It also means that once a repository mode subscription receives a delete, it will only receive changes for that entity. Would it be possible to pass the change type to GetSubscriptions and skip deletes for repository mode subscriptions?
There was a problem hiding this comment.
Oh, right. I think this the first time I've ever really worked in this code. Let me know if this looks right now.
Enkidu93
left a comment
There was a problem hiding this comment.
@Enkidu93 made 5 comments and resolved 2 discussions.
Reviewable status: 1 of 4 files reviewed, 3 unresolved discussions (waiting on ddaspit and pmachapman).
src/DataAccess/src/SIL.DataAccess/MemoryRepository.cs line 360 at r1 (raw file):
Previously, pmachapman (Peter Chapman) wrote…
NIT: You could combine these two conditions
Done.
src/DataAccess/src/SIL.DataAccess/MemorySubscription.cs line 23 at r1 (raw file):
Previously, pmachapman (Peter Chapman) wrote…
NIT: You could remove this before the
ifstatement then remove theelseblock.
Done.
src/DataAccess/src/SIL.DataAccess/MongoSubscription.cs line 15 at r1 (raw file):
Previously, pmachapman (Peter Chapman) wrote…
You use a
ConcurrentQueuein theMemorySubscription, but just a normalQueuehere. I would have thought both classes would have the same thread safety requirements?
Done. You're right.
| foreach (KeyValuePair<MemorySubscription<T>, Func<T, bool>> kvp in _subscriptions) | ||
| { | ||
| if (kvp.Key.Change.Entity is null) | ||
| if (kvp.Key.Mode == SubscriptionMode.Repository) |
There was a problem hiding this comment.
That makes sense. Done.
| { | ||
| await TaskTimeout(_changeEvent.WaitAsync, timeout ?? Timeout.InfiniteTimeSpan, cancellationToken) | ||
| .ConfigureAwait(false); | ||
| if (Mode == SubscriptionMode.Repository) |
There was a problem hiding this comment.
Good catch. I think this fixes it. Let me know if I'm not following.
Enkidu93
left a comment
There was a problem hiding this comment.
@Enkidu93 made 2 comments.
Reviewable status: 1 of 4 files reviewed, 3 unresolved discussions (waiting on ddaspit and pmachapman).
| foreach (KeyValuePair<MemorySubscription<T>, Func<T, bool>> kvp in _subscriptions) | ||
| { | ||
| if (kvp.Key.Change.Entity is null) | ||
| if (kvp.Key.Mode == SubscriptionMode.Repository) |
There was a problem hiding this comment.
Oh, right. I think this the first time I've ever really worked in this code. Let me know if this looks right now.
| { | ||
| await TaskTimeout(_changeEvent.WaitAsync, timeout ?? Timeout.InfiniteTimeSpan, cancellationToken) | ||
| .ConfigureAwait(false); | ||
| if (Mode == SubscriptionMode.Repository) |
ddaspit
left a comment
There was a problem hiding this comment.
@ddaspit reviewed 4 files and all commit messages, made 1 comment, and resolved 2 discussions.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on Enkidu93 and pmachapman).
src/DataAccess/src/SIL.DataAccess/MemorySubscription.cs line 24 at r3 (raw file):
{ TimeSpan remainingTimeout = Timeout.InfiniteTimeSpan; if (timeout != null)
You should also check that timeout is not equal to Timeout.InfiniteTimeSpan.
Enkidu93
left a comment
There was a problem hiding this comment.
@Enkidu93 made 1 comment.
Reviewable status: 3 of 4 files reviewed, 2 unresolved discussions (waiting on ddaspit and pmachapman).
src/DataAccess/src/SIL.DataAccess/MemorySubscription.cs line 24 at r3 (raw file):
Previously, ddaspit (Damien Daspit) wrote…
You should also check that
timeoutis not equal toTimeout.InfiniteTimeSpan.
Good point, done.
ddaspit
left a comment
There was a problem hiding this comment.
@ddaspit reviewed 1 file and all commit messages, made 1 comment, and resolved 1 discussion.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on pmachapman).
d100dfb to
b744f70
Compare
b744f70 to
df554cd
Compare
Enkidu93
left a comment
There was a problem hiding this comment.
@Enkidu93 resolved 1 discussion and dismissed @pmachapman from a discussion.
Reviewable status: all files reviewed (commit messages unreviewed), all discussions resolved (waiting on pmachapman).
pmachapman
left a comment
There was a problem hiding this comment.
@pmachapman reviewed 3 files and all commit messages, and made 1 comment.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on Enkidu93).
Fixes #1047.
I decided not to add a just-poll-the-pending-builds backstop. It seemed to me that we should get the subscription working properly and that something beyond that could be a bit hacky/overly complicated. But I'm open to adding it if you guys would prefer.
This change is