72
72
from synapse .logging .context import run_in_background
73
73
from synapse .metrics .background_process_metrics import run_as_background_process
74
74
from synapse .storage .databases .main import DataStore
75
- from synapse .types import JsonDict
75
+ from synapse .types import DeviceListUpdates , JsonDict
76
76
from synapse .util import Clock
77
77
78
78
if TYPE_CHECKING :
@@ -122,6 +122,7 @@ def enqueue_for_appservice(
122
122
events : Optional [Collection [EventBase ]] = None ,
123
123
ephemeral : Optional [Collection [JsonDict ]] = None ,
124
124
to_device_messages : Optional [Collection [JsonDict ]] = None ,
125
+ device_list_summary : Optional [DeviceListUpdates ] = None ,
125
126
) -> None :
126
127
"""
127
128
Enqueue some data to be sent off to an application service.
@@ -133,10 +134,18 @@ def enqueue_for_appservice(
133
134
to_device_messages: The to-device messages to send. These differ from normal
134
135
to-device messages sent to clients, as they have 'to_device_id' and
135
136
'to_user_id' fields.
137
+ device_list_summary: A summary of users that the application service either needs
138
+ to refresh the device lists of, or those that the application service need no
139
+ longer track the device lists of.
136
140
"""
137
141
# We purposefully allow this method to run with empty events/ephemeral
138
142
# collections, so that callers do not need to check iterable size themselves.
139
- if not events and not ephemeral and not to_device_messages :
143
+ if (
144
+ not events
145
+ and not ephemeral
146
+ and not to_device_messages
147
+ and not device_list_summary
148
+ ):
140
149
return
141
150
142
151
if events :
@@ -147,6 +156,10 @@ def enqueue_for_appservice(
147
156
self .queuer .queued_to_device_messages .setdefault (appservice .id , []).extend (
148
157
to_device_messages
149
158
)
159
+ if device_list_summary :
160
+ self .queuer .queued_device_list_summaries .setdefault (
161
+ appservice .id , []
162
+ ).append (device_list_summary )
150
163
151
164
# Kick off a new application service transaction
152
165
self .queuer .start_background_request (appservice )
@@ -169,6 +182,8 @@ def __init__(
169
182
self .queued_ephemeral : Dict [str , List [JsonDict ]] = {}
170
183
# dict of {service_id: [to_device_message_json]}
171
184
self .queued_to_device_messages : Dict [str , List [JsonDict ]] = {}
185
+ # dict of {service_id: [device_list_summary]}
186
+ self .queued_device_list_summaries : Dict [str , List [DeviceListUpdates ]] = {}
172
187
173
188
# the appservices which currently have a transaction in flight
174
189
self .requests_in_flight : Set [str ] = set ()
@@ -212,7 +227,35 @@ async def _send_request(self, service: ApplicationService) -> None:
212
227
]
213
228
del all_to_device_messages [:MAX_TO_DEVICE_MESSAGES_PER_TRANSACTION ]
214
229
215
- if not events and not ephemeral and not to_device_messages_to_send :
230
+ # Consolidate any pending device list summaries into a single, up-to-date
231
+ # summary.
232
+ # Note: this code assumes that in a single DeviceListUpdates, a user will
233
+ # never be in both "changed" and "left" sets.
234
+ device_list_summary = DeviceListUpdates ()
235
+ for summary in self .queued_device_list_summaries .get (service .id , []):
236
+ # For every user in the incoming "changed" set:
237
+ # * Remove them from the existing "left" set if necessary
238
+ # (as we need to start tracking them again)
239
+ # * Add them to the existing "changed" set if necessary.
240
+ device_list_summary .left .difference_update (summary .changed )
241
+ device_list_summary .changed .update (summary .changed )
242
+
243
+ # For every user in the incoming "left" set:
244
+ # * Remove them from the existing "changed" set if necessary
245
+ # (we no longer need to track them)
246
+ # * Add them to the existing "left" set if necessary.
247
+ device_list_summary .changed .difference_update (summary .left )
248
+ device_list_summary .left .update (summary .left )
249
+ self .queued_device_list_summaries .clear ()
250
+
251
+ if (
252
+ not events
253
+ and not ephemeral
254
+ and not to_device_messages_to_send
255
+ # DeviceListUpdates is True if either the 'changed' or 'left' sets have
256
+ # at least one entry, otherwise False
257
+ and not device_list_summary
258
+ ):
216
259
return
217
260
218
261
one_time_key_counts : Optional [TransactionOneTimeKeyCounts ] = None
@@ -240,6 +283,7 @@ async def _send_request(self, service: ApplicationService) -> None:
240
283
to_device_messages_to_send ,
241
284
one_time_key_counts ,
242
285
unused_fallback_keys ,
286
+ device_list_summary ,
243
287
)
244
288
except Exception :
245
289
logger .exception ("AS request failed" )
@@ -322,6 +366,7 @@ async def send(
322
366
to_device_messages : Optional [List [JsonDict ]] = None ,
323
367
one_time_key_counts : Optional [TransactionOneTimeKeyCounts ] = None ,
324
368
unused_fallback_keys : Optional [TransactionUnusedFallbackKeys ] = None ,
369
+ device_list_summary : Optional [DeviceListUpdates ] = None ,
325
370
) -> None :
326
371
"""
327
372
Create a transaction with the given data and send to the provided
@@ -336,6 +381,7 @@ async def send(
336
381
appservice devices in the transaction.
337
382
unused_fallback_keys: Lists of unused fallback keys for relevant
338
383
appservice devices in the transaction.
384
+ device_list_summary: The device list summary to include in the transaction.
339
385
"""
340
386
try :
341
387
txn = await self .store .create_appservice_txn (
@@ -345,6 +391,7 @@ async def send(
345
391
to_device_messages = to_device_messages or [],
346
392
one_time_key_counts = one_time_key_counts or {},
347
393
unused_fallback_keys = unused_fallback_keys or {},
394
+ device_list_summary = device_list_summary or DeviceListUpdates (),
348
395
)
349
396
service_is_up = await self ._is_service_up (service )
350
397
if service_is_up :
0 commit comments