Send EDUs over /transaction and drop device stuff
parent
f807c7291f
commit
5cb3d237ae
|
@ -35,10 +35,11 @@ class ApplicationServiceState:
|
||||||
class AppServiceTransaction:
|
class AppServiceTransaction:
|
||||||
"""Represents an application service transaction."""
|
"""Represents an application service transaction."""
|
||||||
|
|
||||||
def __init__(self, service, id, events):
|
def __init__(self, service, id, events, ephemeral=None):
|
||||||
self.service = service
|
self.service = service
|
||||||
self.id = id
|
self.id = id
|
||||||
self.events = events
|
self.events = events
|
||||||
|
self.ephemeral = ephemeral
|
||||||
|
|
||||||
async def send(self, as_api: ApplicationServiceApi) -> bool:
|
async def send(self, as_api: ApplicationServiceApi) -> bool:
|
||||||
"""Sends this transaction using the provided AS API interface.
|
"""Sends this transaction using the provided AS API interface.
|
||||||
|
@ -49,7 +50,10 @@ class AppServiceTransaction:
|
||||||
True if the transaction was sent.
|
True if the transaction was sent.
|
||||||
"""
|
"""
|
||||||
return await as_api.push_bulk(
|
return await as_api.push_bulk(
|
||||||
service=self.service, events=self.events, txn_id=self.id
|
service=self.service,
|
||||||
|
events=self.events,
|
||||||
|
ephemeral=self.ephemeral,
|
||||||
|
txn_id=self.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def complete(self, store: "DataStore") -> None:
|
async def complete(self, store: "DataStore") -> None:
|
||||||
|
|
|
@ -201,33 +201,7 @@ class ApplicationServiceApi(SimpleHttpClient):
|
||||||
key = (service.id, protocol)
|
key = (service.id, protocol)
|
||||||
return await self.protocol_meta_cache.wrap(key, _get)
|
return await self.protocol_meta_cache.wrap(key, _get)
|
||||||
|
|
||||||
async def push_ephemeral(self, service, events, to_device=None, device_lists=None):
|
async def push_bulk(self, service, events, ephemeral=None, txn_id=None):
|
||||||
if service.url is None:
|
|
||||||
return True
|
|
||||||
if service.supports_ephemeral is False:
|
|
||||||
return True
|
|
||||||
|
|
||||||
uri = service.url + (
|
|
||||||
"%s/uk.half-shot.appservice/ephemeral" % APP_SERVICE_PREFIX
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
await self.put_json(
|
|
||||||
uri=uri,
|
|
||||||
json_body={
|
|
||||||
"events": events,
|
|
||||||
"device_messages": to_device,
|
|
||||||
"device_lists": device_lists,
|
|
||||||
},
|
|
||||||
args={"access_token": service.hs_token},
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
except CodeMessageException as e:
|
|
||||||
logger.warning("push_ephemeral to %s received %s", uri, e.code)
|
|
||||||
except Exception as ex:
|
|
||||||
logger.warning("push_ephemeral to %s threw exception %s", uri, ex)
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def push_bulk(self, service, events, txn_id=None):
|
|
||||||
if service.url is None:
|
if service.url is None:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -241,11 +215,12 @@ class ApplicationServiceApi(SimpleHttpClient):
|
||||||
txn_id = str(txn_id)
|
txn_id = str(txn_id)
|
||||||
|
|
||||||
uri = service.url + ("/transactions/%s" % urllib.parse.quote(txn_id))
|
uri = service.url + ("/transactions/%s" % urllib.parse.quote(txn_id))
|
||||||
|
body = {"events": events}
|
||||||
|
if ephemeral:
|
||||||
|
body["uk.half-shot.appservice.ephemeral"] = ephemeral
|
||||||
try:
|
try:
|
||||||
await self.put_json(
|
await self.put_json(
|
||||||
uri=uri,
|
uri=uri, json_body=body, args={"access_token": service.hs_token},
|
||||||
json_body={"events": events},
|
|
||||||
args={"access_token": service.hs_token},
|
|
||||||
)
|
)
|
||||||
sent_transactions_counter.labels(service.id).inc()
|
sent_transactions_counter.labels(service.id).inc()
|
||||||
sent_events_counter.labels(service.id).inc(len(events))
|
sent_events_counter.labels(service.id).inc(len(events))
|
||||||
|
|
|
@ -85,9 +85,8 @@ class ApplicationServiceScheduler:
|
||||||
def submit_event_for_as(self, service, event):
|
def submit_event_for_as(self, service, event):
|
||||||
self.queuer.enqueue(service, event)
|
self.queuer.enqueue(service, event)
|
||||||
|
|
||||||
async def submit_ephemeral_events_for_as(self, service, events):
|
def submit_ephemeral_events_for_as(self, service, events):
|
||||||
if self.txn_ctrl.is_service_up(service):
|
self.queuer.enqueue_ephemeral(service, events)
|
||||||
await self.as_api.push_ephemeral(service, events)
|
|
||||||
|
|
||||||
|
|
||||||
class _ServiceQueuer:
|
class _ServiceQueuer:
|
||||||
|
@ -100,6 +99,7 @@ class _ServiceQueuer:
|
||||||
|
|
||||||
def __init__(self, txn_ctrl, clock):
|
def __init__(self, txn_ctrl, clock):
|
||||||
self.queued_events = {} # dict of {service_id: [events]}
|
self.queued_events = {} # dict of {service_id: [events]}
|
||||||
|
self.queued_ephemeral = {} # dict of {service_id: [events]}
|
||||||
|
|
||||||
# the appservices which currently have a transaction in flight
|
# the appservices which currently have a transaction in flight
|
||||||
self.requests_in_flight = set()
|
self.requests_in_flight = set()
|
||||||
|
@ -115,10 +115,22 @@ class _ServiceQueuer:
|
||||||
return
|
return
|
||||||
|
|
||||||
run_as_background_process(
|
run_as_background_process(
|
||||||
"as-sender-%s" % (service.id,), self._send_request, service
|
"as-sender-%s" % (service.id), self._send_request, service
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _send_request(self, service):
|
def enqueue_ephemeral(self, service, events):
|
||||||
|
self.queued_ephemeral.setdefault(service.id, []).extend(events)
|
||||||
|
|
||||||
|
# start a sender for this appservice if we don't already have one
|
||||||
|
|
||||||
|
if service.id in self.requests_in_flight:
|
||||||
|
return
|
||||||
|
|
||||||
|
run_as_background_process(
|
||||||
|
"as-sender-%s" % (service.id), self._send_request, service
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _send_request(self, service, ephemeral=None):
|
||||||
# sanity-check: we shouldn't get here if this service already has a sender
|
# sanity-check: we shouldn't get here if this service already has a sender
|
||||||
# running.
|
# running.
|
||||||
assert service.id not in self.requests_in_flight
|
assert service.id not in self.requests_in_flight
|
||||||
|
@ -127,10 +139,11 @@ class _ServiceQueuer:
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
events = self.queued_events.pop(service.id, [])
|
events = self.queued_events.pop(service.id, [])
|
||||||
if not events:
|
ephemeral = self.queued_ephemeral.pop(service.id, [])
|
||||||
|
if not events and not ephemeral:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
await self.txn_ctrl.send(service, events)
|
await self.txn_ctrl.send(service, events, ephemeral)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("AS request failed")
|
logger.exception("AS request failed")
|
||||||
finally:
|
finally:
|
||||||
|
@ -162,9 +175,9 @@ class _TransactionController:
|
||||||
# for UTs
|
# for UTs
|
||||||
self.RECOVERER_CLASS = _Recoverer
|
self.RECOVERER_CLASS = _Recoverer
|
||||||
|
|
||||||
async def send(self, service, events):
|
async def send(self, service, events, ephemeral=None):
|
||||||
try:
|
try:
|
||||||
txn = await self.store.create_appservice_txn(service=service, events=events)
|
txn = await self.store.create_appservice_txn(service=service, events=events, ephemeral=ephemeral)
|
||||||
service_is_up = await self.is_service_up(service)
|
service_is_up = await self.is_service_up(service)
|
||||||
if service_is_up:
|
if service_is_up:
|
||||||
sent = await txn.send(self.as_api)
|
sent = await txn.send(self.as_api)
|
||||||
|
|
|
@ -179,39 +179,27 @@ class ApplicationServicesHandler:
|
||||||
logger.info("Checking interested services for %s" % (stream_key))
|
logger.info("Checking interested services for %s" % (stream_key))
|
||||||
with Measure(self.clock, "notify_interested_services_ephemeral"):
|
with Measure(self.clock, "notify_interested_services_ephemeral"):
|
||||||
for service in services:
|
for service in services:
|
||||||
events = []
|
|
||||||
if stream_key == "typing_key":
|
if stream_key == "typing_key":
|
||||||
events = await self._handle_typing(service, new_token)
|
events = await self._handle_typing(service, new_token)
|
||||||
|
if events:
|
||||||
|
self.scheduler.submit_ephemeral_events_for_as(service, events)
|
||||||
|
# We don't persist the token for typing_key for performance reasons
|
||||||
elif stream_key == "receipt_key":
|
elif stream_key == "receipt_key":
|
||||||
events = await self._handle_receipts(service)
|
events = await self._handle_receipts(service)
|
||||||
elif stream_key == "presence_key":
|
if events:
|
||||||
events = await self._handle_as_presence(service, users)
|
self.scheduler.submit_ephemeral_events_for_as(service, events)
|
||||||
elif stream_key == "device_list_key":
|
|
||||||
# Check if the device lists have changed for any of the users we are interested in
|
|
||||||
events = await self._handle_device_list(service, users, new_token)
|
|
||||||
elif stream_key == "to_device_key":
|
|
||||||
# Check the inbox for any users the bridge owns
|
|
||||||
events = await self._handle_to_device(service, users, new_token)
|
|
||||||
if events:
|
|
||||||
# TODO: Do in background?
|
|
||||||
await self.scheduler.submit_ephemeral_events_for_as(
|
|
||||||
service, events, new_token
|
|
||||||
)
|
|
||||||
# We don't persist the token for typing_key
|
|
||||||
if stream_key == "presence_key":
|
|
||||||
await self.store.set_type_stream_id_for_appservice(
|
|
||||||
service, "presence", new_token
|
|
||||||
)
|
|
||||||
elif stream_key == "receipt_key":
|
|
||||||
await self.store.set_type_stream_id_for_appservice(
|
await self.store.set_type_stream_id_for_appservice(
|
||||||
service, "read_receipt", new_token
|
service, "read_receipt", new_token
|
||||||
)
|
)
|
||||||
elif stream_key == "to_device_key":
|
elif stream_key == "presence_key":
|
||||||
await self.store.set_type_stream_id_for_appservice(
|
events = await self._handle_as_presence(service, users)
|
||||||
service, "to_device", new_token
|
if events:
|
||||||
)
|
self.scheduler.submit_ephemeral_events_for_as(service, events)
|
||||||
|
await self.store.set_type_stream_id_for_appservice(
|
||||||
|
service, "presence", new_token
|
||||||
|
)
|
||||||
|
|
||||||
async def _handle_typing(self, service, new_token):
|
async def _handle_typing(self, service: ApplicationService, new_token: int):
|
||||||
typing_source = self.event_sources.sources["typing"]
|
typing_source = self.event_sources.sources["typing"]
|
||||||
# Get the typing events from just before current
|
# Get the typing events from just before current
|
||||||
typing, _key = await typing_source.get_new_events_as(
|
typing, _key = await typing_source.get_new_events_as(
|
||||||
|
@ -223,7 +211,7 @@ class ApplicationServicesHandler:
|
||||||
)
|
)
|
||||||
return typing
|
return typing
|
||||||
|
|
||||||
async def _handle_receipts(self, service, token: int):
|
async def _handle_receipts(self, service: ApplicationService, token: int):
|
||||||
from_key = await self.store.get_type_stream_id_for_appservice(
|
from_key = await self.store.get_type_stream_id_for_appservice(
|
||||||
service, "read_receipt"
|
service, "read_receipt"
|
||||||
)
|
)
|
||||||
|
@ -233,36 +221,7 @@ class ApplicationServicesHandler:
|
||||||
)
|
)
|
||||||
return receipts
|
return receipts
|
||||||
|
|
||||||
async def _handle_device_list(
|
async def _handle_as_presence(self, service: ApplicationService, users: List[str]):
|
||||||
self, service: ApplicationService, users: List[str], new_token: int
|
|
||||||
):
|
|
||||||
# TODO: Determine if any user have left and report those
|
|
||||||
from_token = await self.store.get_type_stream_id_for_appservice(
|
|
||||||
service, "device_list"
|
|
||||||
)
|
|
||||||
changed_user_ids = await self.store.get_device_changes_for_as(
|
|
||||||
service, from_token, new_token
|
|
||||||
)
|
|
||||||
# Return the
|
|
||||||
return {
|
|
||||||
"type": "m.device_list_update",
|
|
||||||
"content": {"changed": changed_user_ids,},
|
|
||||||
}
|
|
||||||
|
|
||||||
async def _handle_to_device(self, service, users, token):
|
|
||||||
if not any([True for u in users if service.is_interested_in_user(u)]):
|
|
||||||
return False
|
|
||||||
|
|
||||||
since_token = await self.store.get_type_stream_id_for_appservice(
|
|
||||||
service, "to_device"
|
|
||||||
)
|
|
||||||
messages, _ = await self.store.get_new_messages_for_as(
|
|
||||||
service, since_token, token
|
|
||||||
)
|
|
||||||
# This returns user_id -> device_id -> message
|
|
||||||
return messages
|
|
||||||
|
|
||||||
async def _handle_as_presence(self, service, users):
|
|
||||||
events = []
|
events = []
|
||||||
presence_source = self.event_sources.sources["presence"]
|
presence_source = self.event_sources.sources["presence"]
|
||||||
from_key = await self.store.get_type_stream_id_for_appservice(
|
from_key = await self.store.get_type_stream_id_for_appservice(
|
||||||
|
|
|
@ -172,7 +172,7 @@ class ApplicationServiceTransactionWorkerStore(
|
||||||
"application_services_state", {"as_id": service.id}, {"state": state}
|
"application_services_state", {"as_id": service.id}, {"state": state}
|
||||||
)
|
)
|
||||||
|
|
||||||
async def create_appservice_txn(self, service, events):
|
async def create_appservice_txn(self, service, events, ephemeral=None):
|
||||||
"""Atomically creates a new transaction for this application service
|
"""Atomically creates a new transaction for this application service
|
||||||
with the given list of events.
|
with the given list of events.
|
||||||
|
|
||||||
|
@ -207,7 +207,9 @@ class ApplicationServiceTransactionWorkerStore(
|
||||||
"VALUES(?,?,?)",
|
"VALUES(?,?,?)",
|
||||||
(service.id, new_txn_id, event_ids),
|
(service.id, new_txn_id, event_ids),
|
||||||
)
|
)
|
||||||
return AppServiceTransaction(service=service, id=new_txn_id, events=events)
|
return AppServiceTransaction(
|
||||||
|
service=service, id=new_txn_id, events=events, ephemeral=ephemeral
|
||||||
|
)
|
||||||
|
|
||||||
return await self.db_pool.runInteraction(
|
return await self.db_pool.runInteraction(
|
||||||
"create_appservice_txn", _create_appservice_txn
|
"create_appservice_txn", _create_appservice_txn
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
|
|
||||||
from synapse.appservice import ApplicationService
|
|
||||||
from synapse.logging.opentracing import log_kv, set_tag, trace
|
from synapse.logging.opentracing import log_kv, set_tag, trace
|
||||||
from synapse.storage._base import SQLBaseStore, db_to_json, make_in_list_sql_clause
|
from synapse.storage._base import SQLBaseStore, db_to_json, make_in_list_sql_clause
|
||||||
from synapse.storage.database import DatabasePool
|
from synapse.storage.database import DatabasePool
|
||||||
|
@ -30,40 +29,6 @@ class DeviceInboxWorkerStore(SQLBaseStore):
|
||||||
def get_to_device_stream_token(self):
|
def get_to_device_stream_token(self):
|
||||||
return self._device_inbox_id_gen.get_current_token()
|
return self._device_inbox_id_gen.get_current_token()
|
||||||
|
|
||||||
async def get_new_messages_for_as(
|
|
||||||
self,
|
|
||||||
service: ApplicationService,
|
|
||||||
last_stream_id: int,
|
|
||||||
current_stream_id: int,
|
|
||||||
limit: int = 100,
|
|
||||||
) -> Tuple[List[dict], int]:
|
|
||||||
def get_new_messages_for_device_txn(txn):
|
|
||||||
sql = (
|
|
||||||
"SELECT stream_id, message_json, device_id, user_id FROM device_inbox"
|
|
||||||
" WHERE ? < stream_id AND stream_id <= ?"
|
|
||||||
" ORDER BY stream_id ASC"
|
|
||||||
" LIMIT ?"
|
|
||||||
)
|
|
||||||
txn.execute(sql, (last_stream_id, current_stream_id, limit))
|
|
||||||
messages = []
|
|
||||||
|
|
||||||
for row in txn:
|
|
||||||
stream_pos = row[0]
|
|
||||||
if service.is_interested_in_user(row.user_id):
|
|
||||||
msg = db_to_json(row[1])
|
|
||||||
msg.recipient = {
|
|
||||||
"device_id": row.device_id,
|
|
||||||
"user_id": row.user_id,
|
|
||||||
}
|
|
||||||
messages.append(msg)
|
|
||||||
if len(messages) < limit:
|
|
||||||
stream_pos = current_stream_id
|
|
||||||
return messages, stream_pos
|
|
||||||
|
|
||||||
return await self.db_pool.runInteraction(
|
|
||||||
"get_new_messages_for_device", get_new_messages_for_device_txn
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_new_messages_for_device(
|
async def get_new_messages_for_device(
|
||||||
self,
|
self,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
|
|
|
@ -19,7 +19,6 @@ import logging
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple
|
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple
|
||||||
|
|
||||||
from synapse.api.errors import Codes, StoreError
|
from synapse.api.errors import Codes, StoreError
|
||||||
from synapse.appservice import ApplicationService
|
|
||||||
from synapse.logging.opentracing import (
|
from synapse.logging.opentracing import (
|
||||||
get_active_span_text_map,
|
get_active_span_text_map,
|
||||||
set_tag,
|
set_tag,
|
||||||
|
@ -526,31 +525,6 @@ class DeviceWorkerStore(SQLBaseStore):
|
||||||
"get_users_whose_devices_changed", _get_users_whose_devices_changed_txn
|
"get_users_whose_devices_changed", _get_users_whose_devices_changed_txn
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_device_changes_for_as(
|
|
||||||
self,
|
|
||||||
service: ApplicationService,
|
|
||||||
last_stream_id: int,
|
|
||||||
current_stream_id: int,
|
|
||||||
limit: int = 100,
|
|
||||||
) -> Tuple[List[dict], int]:
|
|
||||||
def get_device_changes_for_as_txn(txn):
|
|
||||||
sql = (
|
|
||||||
"SELECT DISTINCT user_ids FROM device_lists_stream"
|
|
||||||
" WHERE ? < stream_id AND stream_id <= ?"
|
|
||||||
" ORDER BY stream_id ASC"
|
|
||||||
" LIMIT ?"
|
|
||||||
)
|
|
||||||
txn.execute(sql, (last_stream_id, current_stream_id, limit))
|
|
||||||
rows = txn.fetchall()
|
|
||||||
users = []
|
|
||||||
for user in db_to_json(rows[0]):
|
|
||||||
if await service.is_interested_in_presence(user):
|
|
||||||
users.append(user)
|
|
||||||
|
|
||||||
return await self.db_pool.runInteraction(
|
|
||||||
"get_device_changes_for_as", get_device_changes_for_as_txn
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_users_whose_signatures_changed(
|
async def get_users_whose_signatures_changed(
|
||||||
self, user_id: str, from_key: int
|
self, user_id: str, from_key: int
|
||||||
) -> Set[str]:
|
) -> Set[str]:
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
/* Copyright 2020 The Matrix.org Foundation C.I.C
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* for some reason, we have accumulated duplicate entries in
|
|
||||||
* device_lists_outbound_pokes, which makes prune_outbound_device_list_pokes less
|
|
||||||
* efficient.
|
|
||||||
*/
|
|
||||||
|
|
||||||
ALTER TABLE application_services_state
|
|
||||||
ADD COLUMN device_list_stream_id INT;
|
|
||||||
|
|
||||||
ALTER TABLE application_services_state
|
|
||||||
ADD COLUMN device_message_stream_id INT;
|
|
Loading…
Reference in New Issue