Skip to content

Commit a21cc09

Browse files
committed
Cache update worker: send broadcast WebSockets message after update
This can be disabled by setting SPATIAL_UPDATE_CLIENT_NOTIFICATIONS = False in the settings.
1 parent d8dc174 commit a21cc09

5 files changed

Lines changed: 76 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Miscellaneous:
1212

1313
- The skeleton reroot API now also returns the new root node's edition time.
1414

15+
- Grid cache worker: send broadcast message after updates using WebSockets. This
16+
can be disabled by settin `SPATIAL_UPDATE_CLIENT_NOTIFICATIONS = False` in
17+
`settings.py`.
18+
1519
### Bug fixes
1620

1721

django/applications/catmaid/consumers.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
logger = logging.getLogger(__name__)
1212

1313

14+
broadcast_group_name = 'broadcast'
15+
16+
1417
def get_user_group_name(user_id):
1518
return f"updates-{user_id}"
1619

@@ -24,8 +27,9 @@ def connect(self):
2427
if not self.channel_layer:
2528
logger.error("UpdateConsumer: can't handle WebSockets connection, no channels layer")
2629
return
27-
# Add user to the matching user group
30+
# Add user to the matching user group and to broadcast group
2831
user = self.scope["user"]
32+
async_to_sync(self.channel_layer.group_add)(broadcast_group_name, self.channel_name)
2933
async_to_sync(self.channel_layer.group_add)(get_user_group_name(user.id), self.channel_name)
3034
self.accept()
3135

@@ -37,6 +41,7 @@ def disconnect(self, message):
3741
logger.error("UpdateConsumer: can't handle WebSockets disconnect, no channels layer")
3842
return
3943
user = self.scope["user"]
44+
async_to_sync(self.channel_layer.group_discard)(broadcast_group_name, self.channel_name)
4045
async_to_sync(self.channel_layer.group_discard)(get_user_group_name(user.id), self.channel_name)
4146

4247
def receive(self, *, text_data):
@@ -125,3 +130,43 @@ def publish_message_to_broker(message, routing_key):
125130
routing_key=routing_key,
126131
retry=False,
127132
)
133+
134+
135+
def msg_all(event_name, data:str="", data_type:str="text", is_raw_data:bool=False,
136+
ignore_missing:bool=True) -> None:
137+
"""Send a message to a user. This message will contain a dictionary with the
138+
field <data_type> with content <data> if raw data is requested, otherwise
139+
with a dictionary. Its fields are "event" for the <event_name> and "payload"
140+
for <data>."""
141+
if is_raw_data:
142+
payload = data
143+
else:
144+
payload = json.dumps({
145+
"event": event_name,
146+
"payload": data
147+
})
148+
# Broadcast to listening sockets
149+
try:
150+
logger.info('attempting send broadcast msg')
151+
if is_in_celery_task():
152+
logger.info('sending broadcast msg from celery task')
153+
publish_message_to_broker({
154+
'type': 'user.message',
155+
'data': payload,
156+
}, broadcast_group_name)
157+
else:
158+
channel_layer = get_channel_layer()
159+
# Without any channel layer, there is no point in trying to send a
160+
# message.
161+
if not channel_layer:
162+
return
163+
logger.info('sending msg')
164+
async_to_sync(channel_layer.group_send)(broadcast_group_name, {
165+
'type': 'user.message',
166+
'data': payload,
167+
})
168+
except KeyError as e:
169+
if ignore_missing:
170+
pass
171+
else:
172+
raise e

django/applications/catmaid/management/commands/catmaid_cache_update_worker.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from django.core.management.base import BaseCommand
1212
from django.db import connection
1313

14+
from catmaid.consumers import msg_all
1415
from catmaid.control.edge import get_intersected_grid_cells
1516
from catmaid.control.node import (get_configured_node_providers,
1617
GridCachedNodeProvider, Postgis3dNodeProvider, update_grid_cell)
@@ -45,6 +46,7 @@ def update(self, updates, cursor):
4546
# Batch of grids to update during one run
4647
updated_cells = 0
4748
updated_grids = set()
49+
updated_cell_refs = []
4850
for update in updates:
4951
g = grid_map[update['grid_id']]
5052
w_i, h_i, d_i = update['x'], update['y'], update['z']
@@ -61,21 +63,34 @@ def update(self, updates, cursor):
6163
if g.hidden_last_editor_id:
6264
params['hidden_last_editor_id'] = int(g.hidden_last_editor_id)
6365

64-
added = update_grid_cell(g.project_id, g.id, w_i, h_i, d_i,
66+
updated = update_grid_cell(g.project_id, g.id, w_i, h_i, d_i,
6567
g.cell_width, g.cell_height, g.cell_depth,
6668
params, g.allow_empty, g.n_lod_levels, g.lod_min_bucket_size,
6769
g.lod_strategy, g.has_json_data, g.has_json_text_data,
6870
g.has_msgpack_data, provider=provider, cursor=cursor,
6971
delete_empty=True)
7072

71-
if added:
73+
if updated:
7274
updated_cells += 1
7375
updated_grids.add(g.id)
76+
updated_cell_refs.add([{
77+
'project_id': g.project_id,
78+
'grid_id', g.id,
79+
'x': w_i,
80+
'y': h_i,
81+
'z': d_i,
82+
})
7483

7584
# TODO: delete in batches
7685
DirtyNodeGridCacheCell.objects.filter(grid_id=g.id, x_index=w_i,
7786
y_index=h_i, z_index=d_i).delete()
7887

88+
# Optionally, let users know about updated cells
89+
if settings.SPATIAL_UPDATE_CLIENT_NOTIFICATIONS:
90+
msg_all('grid-cache-update', {
91+
'updated_cells': updated_cell_refs,
92+
})
93+
7994
logger.debug(f'Updated {updated_cells} grid cell(s) in {len(updated_grids)} grid cache(s)')
8095

8196

django/applications/catmaid/static/js/init.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,11 @@ var project;
15701570
CATMAID.msg("New message", payload.message_title);
15711571
client.check_messages(true);
15721572
}
1573+
], [
1574+
'grid-cache-update',
1575+
function (client, payload) {
1576+
// Ignore for now
1577+
}
15731578
], [
15741579
'unknown',
15751580
function(message) {

django/projects/mysite/settings_base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,10 @@
535535
# connector links).
536536
SPATIAL_UPDATE_NOTIFICATIONS = False
537537

538+
# Specifies whether grid cache update workser will notify all clients about
539+
# recomputed dirty cells using WebSockets.
540+
SPATIAL_UPDATE_CLIENT_NOTIFICATIONS = True
541+
538542
# On statup, the default client instance settings can be populated based on a
539543
# JSON string, representing a list of objects with a "key" field and a "value"
540544
# field. These settings will only be applied if they exist already.

0 commit comments

Comments
 (0)