-
Notifications
You must be signed in to change notification settings - Fork 221
Notification events resolving and rendering in batches #4722
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
base: develop
Are you sure you want to change the base?
Conversation
…cation is disabled for the time being).
📱 Scan the QR code below to install the build (arm64 only) for this PR. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #4722 +/- ##
========================================
Coverage 80.09% 80.10%
========================================
Files 2129 2130 +1
Lines 56416 56493 +77
Branches 7052 7070 +18
========================================
+ Hits 45188 45254 +66
- Misses 8811 8815 +4
- Partials 2417 2424 +7 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
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.
Thanks for improving this part!
Some early remarks.
|
||
interface NotificationService { | ||
suspend fun getNotification(roomId: RoomId, eventId: EventId): Result<NotificationData?> | ||
suspend fun getNotification(sessionId: SessionId, roomId: RoomId, eventId: EventId): Result<NotificationData?> | ||
suspend fun getNotifications(sessionId: SessionId, ids: Map<RoomId, List<EventId>>): Result<Map<EventId, NotificationData?>> |
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.
At first sight, it's strange that the return type is not
Result<Map<RoomId, List<NotificationData>>>
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 was like that originally, but where it's immediately used later (here) having the notifications grouped by room id doesn't really help, we'd have to first search for the room, then use notifications.find { it.eventId == eventId }
. Using having the id -> data mapping is more optimised.
suspend fun resolveEvents( | ||
sessionId: SessionId, | ||
notificationEventRequests: List<NotificationEventRequest> | ||
): Result<Map<EventId, ResolvedPushEvent?>> |
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.
Same type of remark, why don't we have a return type like:
Map<RoomId, List<ResolvedPushEvent>>
?
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.
Here it might actually make sense to do that for the pushHistoryService.onSuccess
and failure callbacks, searching might be a bit faster even if later we need to either iterate by room then event id or just flatten everything.
Maybe it would even make sense to use the NotificationEventRequest
as the key itself? It has the session id, room and event ids to make fetching a single operation.
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.
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.
More comments. I will test the code on a real device.
@@ -30,8 +30,9 @@ class DefaultOnMissedCallNotificationHandler @Inject constructor( | |||
// Resolve the event and add a notification for it, at this point it should no longer be a ringing one | |||
val notificationData = matrixClientProvider.getOrRestore(sessionId).getOrNull() | |||
?.notificationService() | |||
?.getNotification(roomId, eventId) | |||
?.getNotifications(sessionId, mapOf(roomId to listOf(eventId))) |
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 looks weird to have to provide a sessionId
here, since we are using a method from a service of a matrix client.
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.
True, but RustNotificationService
doesn't have a reference to the matrix client. Maybe I should just add a SessionId
as a constructor param for it. WDYT?
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.
Maybe I should just add a
SessionId
as a constructor param for it. WDYT?
Yes please. We're doing it for RustRoomFactory
already for instance.
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.
RustRoomFactory
doesn't use injection though. Adding it here would mean creating a factory abstraction.
Never mind, I looked at the wrong component. I have a severe case of the Mondays, it seems.
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.
coroutineScope.launch { | ||
while (coroutineScope.isActive) { | ||
// Wait for a batch of requests to be received in a specified time window | ||
delay(BATCH_WINDOW_MS.milliseconds) |
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.
Are you sure this code will behave as a first throttler?
If we get a Push just before the delay is ended, it will trigger the request without waiting for any other potential push. I have not checked in practice how much time we have between mutliple pending push received from Firebase though.
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 doesn't have to act as a throttle operation, but as a window one, grouping events that happen in an interval in batches. Those batches won't always be optimal, since as you say, if you receive one after one window closes it'll be processed on the next one.
If you think it's worth it implementing it more in a throttle/debounce way (without discarding items as those operators usually do) I can try to do that. There is the possibility that this means we chain lots of 'received item, wait for X ms' operations and end up delaying the processing for longer, but it's not really likely to happen in the real world except maybe for when you have several pushes in firebase that couldn't be delivered, the device reconnects and it starts receiving the pending pushes.
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.
Done in bd27b50, it seems to work quite well.
val notificationManager = NotificationManagerCompat.from(context) | ||
val previousNotification = notificationManager.activeNotifications.find { it.tag == roomInfo.roomId.value } | ||
val elapsed = System.currentTimeMillis() - (previousNotification?.postTime ?: 0L) | ||
Timber.d("Creating noisy notification for room ${roomInfo.roomId.value} with elapsed time $elapsed") |
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.
Move this log into the if block (not sure)
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.
Ooops, this should actually be gone. It was done for an experiment and I removed part of it, but apparently I kept this by mistake.
|
Content
NotificationResolverQueue
type that enqueues notification fetching requests received in a certain time window, and resolves them in batches, emitting the results when done.DefaultPushHandler
now subscribes to these results and also processes them in batches, categorising the items and redirecting them to their callbacks (onRedactedEventReceived
,handleRingingCallEvent
, etc.).DefaultNotificationDrawerManager
now has aonNotifiableEventsReceived
to render several notifications at the same time.Motivation and context
The latest SDK version allows clients to fetch several notifications as a group so we need to run a single sliding sync request for them. However, since the items were returned at the same time and immediately sent to the
NotificationManager
so they can be displayed, the OS thought we were spamming the notifications and 'muted' (or discarded) some of them. Instead, we had to rework how we process the resolved notifications.Tests
Tested devices
Checklist