Merge pull request #150 from matrix-org/notifier_unify
Make v1 and v2 client APIs interact with the notifier in the same way.
This commit is contained in:
commit
4770cec7bc
|
@ -16,6 +16,7 @@
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.util.logutils import log_function
|
from synapse.util.logutils import log_function
|
||||||
|
from synapse.util.async import run_on_reactor
|
||||||
from synapse.types import StreamToken
|
from synapse.types import StreamToken
|
||||||
import synapse.metrics
|
import synapse.metrics
|
||||||
|
|
||||||
|
@ -49,13 +50,9 @@ class _NotificationListener(object):
|
||||||
so that it can remove itself from the indexes in the Notifier class.
|
so that it can remove itself from the indexes in the Notifier class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, user, rooms, from_token, limit, timeout, deferred,
|
def __init__(self, user, rooms, deferred, appservice=None):
|
||||||
appservice=None):
|
|
||||||
self.user = user
|
self.user = user
|
||||||
self.appservice = appservice
|
self.appservice = appservice
|
||||||
self.from_token = from_token
|
|
||||||
self.limit = limit
|
|
||||||
self.timeout = timeout
|
|
||||||
self.deferred = deferred
|
self.deferred = deferred
|
||||||
self.rooms = rooms
|
self.rooms = rooms
|
||||||
self.timer = None
|
self.timer = None
|
||||||
|
@ -63,17 +60,14 @@ class _NotificationListener(object):
|
||||||
def notified(self):
|
def notified(self):
|
||||||
return self.deferred.called
|
return self.deferred.called
|
||||||
|
|
||||||
def notify(self, notifier, events, start_token, end_token):
|
def notify(self, notifier):
|
||||||
""" Inform whoever is listening about the new events. This will
|
""" Inform whoever is listening about the new events. This will
|
||||||
also remove this listener from all the indexes in the Notifier
|
also remove this listener from all the indexes in the Notifier
|
||||||
it knows about.
|
it knows about.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = (events, (start_token, end_token))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.deferred.callback(result)
|
self.deferred.callback(None)
|
||||||
notified_events_counter.inc_by(len(events))
|
|
||||||
except defer.AlreadyCalledError:
|
except defer.AlreadyCalledError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -160,6 +154,7 @@ class Notifier(object):
|
||||||
listening to the room, and any listeners for the users in the
|
listening to the room, and any listeners for the users in the
|
||||||
`extra_users` param.
|
`extra_users` param.
|
||||||
"""
|
"""
|
||||||
|
yield run_on_reactor()
|
||||||
# poke any interested application service.
|
# poke any interested application service.
|
||||||
self.hs.get_handlers().appservice_handler.notify_interested_services(
|
self.hs.get_handlers().appservice_handler.notify_interested_services(
|
||||||
event
|
event
|
||||||
|
@ -167,8 +162,6 @@ class Notifier(object):
|
||||||
|
|
||||||
room_id = event.room_id
|
room_id = event.room_id
|
||||||
|
|
||||||
room_source = self.event_sources.sources["room"]
|
|
||||||
|
|
||||||
room_listeners = self.room_to_listeners.get(room_id, set())
|
room_listeners = self.room_to_listeners.get(room_id, set())
|
||||||
|
|
||||||
_discard_if_notified(room_listeners)
|
_discard_if_notified(room_listeners)
|
||||||
|
@ -199,33 +192,11 @@ class Notifier(object):
|
||||||
|
|
||||||
logger.debug("on_new_room_event listeners %s", listeners)
|
logger.debug("on_new_room_event listeners %s", listeners)
|
||||||
|
|
||||||
# TODO (erikj): Can we make this more efficient by hitting the
|
for listener in listeners:
|
||||||
# db once?
|
try:
|
||||||
|
listener.notify(self)
|
||||||
@defer.inlineCallbacks
|
except:
|
||||||
def notify(listener):
|
logger.exception("Failed to notify listener")
|
||||||
events, end_key = yield room_source.get_new_events_for_user(
|
|
||||||
listener.user,
|
|
||||||
listener.from_token.room_key,
|
|
||||||
listener.limit,
|
|
||||||
)
|
|
||||||
|
|
||||||
if events:
|
|
||||||
end_token = listener.from_token.copy_and_replace(
|
|
||||||
"room_key", end_key
|
|
||||||
)
|
|
||||||
|
|
||||||
listener.notify(
|
|
||||||
self, events, listener.from_token, end_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def eb(failure):
|
|
||||||
logger.exception("Failed to notify listener", failure)
|
|
||||||
|
|
||||||
yield defer.DeferredList(
|
|
||||||
[notify(l).addErrback(eb) for l in listeners],
|
|
||||||
consumeErrors=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
@log_function
|
@log_function
|
||||||
|
@ -235,11 +206,7 @@ class Notifier(object):
|
||||||
|
|
||||||
Will wake up all listeners for the given users and rooms.
|
Will wake up all listeners for the given users and rooms.
|
||||||
"""
|
"""
|
||||||
# TODO(paul): This is horrible, having to manually list every event
|
yield run_on_reactor()
|
||||||
# source here individually
|
|
||||||
presence_source = self.event_sources.sources["presence"]
|
|
||||||
typing_source = self.event_sources.sources["typing"]
|
|
||||||
|
|
||||||
listeners = set()
|
listeners = set()
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
|
@ -256,68 +223,29 @@ class Notifier(object):
|
||||||
|
|
||||||
listeners |= room_listeners
|
listeners |= room_listeners
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
for listener in listeners:
|
||||||
def notify(listener):
|
try:
|
||||||
presence_events, presence_end_key = (
|
listener.notify(self)
|
||||||
yield presence_source.get_new_events_for_user(
|
except:
|
||||||
listener.user,
|
logger.exception("Failed to notify listener")
|
||||||
listener.from_token.presence_key,
|
|
||||||
listener.limit,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
typing_events, typing_end_key = (
|
|
||||||
yield typing_source.get_new_events_for_user(
|
|
||||||
listener.user,
|
|
||||||
listener.from_token.typing_key,
|
|
||||||
listener.limit,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if presence_events or typing_events:
|
|
||||||
end_token = listener.from_token.copy_and_replace(
|
|
||||||
"presence_key", presence_end_key
|
|
||||||
).copy_and_replace(
|
|
||||||
"typing_key", typing_end_key
|
|
||||||
)
|
|
||||||
|
|
||||||
listener.notify(
|
|
||||||
self,
|
|
||||||
presence_events + typing_events,
|
|
||||||
listener.from_token,
|
|
||||||
end_token
|
|
||||||
)
|
|
||||||
|
|
||||||
def eb(failure):
|
|
||||||
logger.error(
|
|
||||||
"Failed to notify listener",
|
|
||||||
exc_info=(
|
|
||||||
failure.type,
|
|
||||||
failure.value,
|
|
||||||
failure.getTracebackObject())
|
|
||||||
)
|
|
||||||
|
|
||||||
yield defer.DeferredList(
|
|
||||||
[notify(l).addErrback(eb) for l in listeners],
|
|
||||||
consumeErrors=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def wait_for_events(self, user, rooms, filter, timeout, callback):
|
def wait_for_events(self, user, rooms, timeout, callback,
|
||||||
|
from_token=StreamToken("s0", "0", "0")):
|
||||||
"""Wait until the callback returns a non empty response or the
|
"""Wait until the callback returns a non empty response or the
|
||||||
timeout fires.
|
timeout fires.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
deferred = defer.Deferred()
|
deferred = defer.Deferred()
|
||||||
|
appservice = yield self.hs.get_datastore().get_app_service_by_user_id(
|
||||||
from_token = StreamToken("s0", "0", "0")
|
user.to_string()
|
||||||
|
)
|
||||||
|
|
||||||
listener = [_NotificationListener(
|
listener = [_NotificationListener(
|
||||||
user=user,
|
user=user,
|
||||||
rooms=rooms,
|
rooms=rooms,
|
||||||
from_token=from_token,
|
|
||||||
limit=1,
|
|
||||||
timeout=timeout,
|
|
||||||
deferred=deferred,
|
deferred=deferred,
|
||||||
|
appservice=appservice,
|
||||||
)]
|
)]
|
||||||
|
|
||||||
if timeout:
|
if timeout:
|
||||||
|
@ -332,7 +260,7 @@ class Notifier(object):
|
||||||
def _timeout_listener():
|
def _timeout_listener():
|
||||||
timed_out[0] = True
|
timed_out[0] = True
|
||||||
timer[0] = None
|
timer[0] = None
|
||||||
listener[0].notify(self, [], from_token, from_token)
|
listener[0].notify(self)
|
||||||
|
|
||||||
# We create multiple notification listeners so we have to manage
|
# We create multiple notification listeners so we have to manage
|
||||||
# canceling the timeout ourselves.
|
# canceling the timeout ourselves.
|
||||||
|
@ -344,10 +272,8 @@ class Notifier(object):
|
||||||
listener[0] = _NotificationListener(
|
listener[0] = _NotificationListener(
|
||||||
user=user,
|
user=user,
|
||||||
rooms=rooms,
|
rooms=rooms,
|
||||||
from_token=from_token,
|
|
||||||
limit=1,
|
|
||||||
timeout=timeout,
|
|
||||||
deferred=deferred,
|
deferred=deferred,
|
||||||
|
appservice=appservice,
|
||||||
)
|
)
|
||||||
self._register_with_keys(listener[0])
|
self._register_with_keys(listener[0])
|
||||||
result = yield callback()
|
result = yield callback()
|
||||||
|
@ -360,65 +286,43 @@ class Notifier(object):
|
||||||
|
|
||||||
defer.returnValue(result)
|
defer.returnValue(result)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
def get_events_for(self, user, rooms, pagination_config, timeout):
|
def get_events_for(self, user, rooms, pagination_config, timeout):
|
||||||
""" For the given user and rooms, return any new events for them. If
|
""" For the given user and rooms, return any new events for them. If
|
||||||
there are no new events wait for up to `timeout` milliseconds for any
|
there are no new events wait for up to `timeout` milliseconds for any
|
||||||
new events to happen before returning.
|
new events to happen before returning.
|
||||||
"""
|
"""
|
||||||
deferred = defer.Deferred()
|
from_token = pagination_config.from_token
|
||||||
|
|
||||||
self._get_events(
|
|
||||||
deferred, user, rooms, pagination_config.from_token,
|
|
||||||
pagination_config.limit, timeout
|
|
||||||
).addErrback(deferred.errback)
|
|
||||||
|
|
||||||
return deferred
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def _get_events(self, deferred, user, rooms, from_token, limit, timeout):
|
|
||||||
if not from_token:
|
if not from_token:
|
||||||
from_token = yield self.event_sources.get_current_token()
|
from_token = yield self.event_sources.get_current_token()
|
||||||
|
|
||||||
appservice = yield self.hs.get_datastore().get_app_service_by_user_id(
|
limit = pagination_config.limit
|
||||||
user.to_string()
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def check_for_updates():
|
||||||
|
events = []
|
||||||
|
end_token = from_token
|
||||||
|
for name, source in self.event_sources.sources.items():
|
||||||
|
keyname = "%s_key" % name
|
||||||
|
stuff, new_key = yield source.get_new_events_for_user(
|
||||||
|
user, getattr(from_token, keyname), limit,
|
||||||
)
|
)
|
||||||
|
events.extend(stuff)
|
||||||
|
end_token = end_token.copy_and_replace(keyname, new_key)
|
||||||
|
|
||||||
listener = _NotificationListener(
|
if events:
|
||||||
user,
|
defer.returnValue((events, (from_token, end_token)))
|
||||||
rooms,
|
|
||||||
from_token,
|
|
||||||
limit,
|
|
||||||
timeout,
|
|
||||||
deferred,
|
|
||||||
appservice=appservice
|
|
||||||
)
|
|
||||||
|
|
||||||
def _timeout_listener():
|
|
||||||
# TODO (erikj): We should probably set to_token to the current
|
|
||||||
# max rather than reusing from_token.
|
|
||||||
# Remove the timer from the listener so we don't try to cancel it.
|
|
||||||
listener.timer = None
|
|
||||||
listener.notify(
|
|
||||||
self,
|
|
||||||
[],
|
|
||||||
listener.from_token,
|
|
||||||
listener.from_token,
|
|
||||||
)
|
|
||||||
|
|
||||||
if timeout:
|
|
||||||
self._register_with_keys(listener)
|
|
||||||
|
|
||||||
yield self._check_for_updates(listener)
|
|
||||||
|
|
||||||
if not timeout:
|
|
||||||
_timeout_listener()
|
|
||||||
else:
|
else:
|
||||||
# Only add the timer if the listener hasn't been notified
|
defer.returnValue(None)
|
||||||
if not listener.notified():
|
|
||||||
listener.timer = self.clock.call_later(
|
result = yield self.wait_for_events(
|
||||||
timeout/1000.0, _timeout_listener
|
user, rooms, timeout, check_for_updates, from_token=from_token
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
if result is None:
|
||||||
|
result = ([], (from_token, from_token))
|
||||||
|
|
||||||
|
defer.returnValue(result)
|
||||||
|
|
||||||
@log_function
|
@log_function
|
||||||
def _register_with_keys(self, listener):
|
def _register_with_keys(self, listener):
|
||||||
|
@ -433,36 +337,6 @@ class Notifier(object):
|
||||||
listener.appservice, set()
|
listener.appservice, set()
|
||||||
).add(listener)
|
).add(listener)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
@log_function
|
|
||||||
def _check_for_updates(self, listener):
|
|
||||||
# TODO (erikj): We need to think about limits across multiple sources
|
|
||||||
events = []
|
|
||||||
|
|
||||||
from_token = listener.from_token
|
|
||||||
limit = listener.limit
|
|
||||||
|
|
||||||
# TODO (erikj): DeferredList?
|
|
||||||
for name, source in self.event_sources.sources.items():
|
|
||||||
keyname = "%s_key" % name
|
|
||||||
|
|
||||||
stuff, new_key = yield source.get_new_events_for_user(
|
|
||||||
listener.user,
|
|
||||||
getattr(from_token, keyname),
|
|
||||||
limit,
|
|
||||||
)
|
|
||||||
|
|
||||||
events.extend(stuff)
|
|
||||||
|
|
||||||
from_token = from_token.copy_and_replace(keyname, new_key)
|
|
||||||
|
|
||||||
end_token = from_token
|
|
||||||
|
|
||||||
if events:
|
|
||||||
listener.notify(self, events, listener.from_token, end_token)
|
|
||||||
|
|
||||||
defer.returnValue(listener)
|
|
||||||
|
|
||||||
def _user_joined_room(self, user, room_id):
|
def _user_joined_room(self, user, room_id):
|
||||||
new_listeners = self.user_to_listeners.get(user, set())
|
new_listeners = self.user_to_listeners.get(user, set())
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue