Prevent queuing lots of setTypingState

@futurize() is now called with parentheses and can take a max_instances
int argument. This is used for setTypingState to not have more than one
queued calls per room and True/False state and avoids bombing the server
with old ephemeral events after a network loss and reconnection.
This commit is contained in:
miruka 2019-04-19 18:21:19 -04:00
parent 188dc6be98
commit aa55ffbc6a
3 changed files with 40 additions and 24 deletions

View File

@ -48,7 +48,7 @@ class Backend(QObject):
@pyqtSlot(str, result="QVariant")
@pyqtSlot(str, bool, result="QVariant")
@futurize
@futurize()
def getUserDisplayName(self, user_id: str, can_block: bool = True) -> str:
if user_id in self._queried_displaynames:
return self._queried_displaynames[user_id]

View File

@ -77,14 +77,14 @@ class Client(QObject):
@pyqtSlot(str)
@pyqtSlot(str, str)
@futurize
@futurize()
def login(self, password: str, device_name: str = "") -> None:
response = self.net.talk(self.nio.login, password, device_name)
self.nio_sync.receive_response(response)
@pyqtSlot(str, str, str)
@futurize
@futurize()
def resumeSession(self, user_id: str, token: str, device_id: str
) -> None:
response = nr.LoginResponse(user_id, device_id, token)
@ -93,7 +93,7 @@ class Client(QObject):
@pyqtSlot()
@futurize
@futurize()
def logout(self) -> None:
self._stop_sync.set()
self.net.http_disconnect()
@ -101,7 +101,7 @@ class Client(QObject):
@pyqtSlot()
@futurize
@futurize()
def startSyncing(self) -> None:
while True:
self._on_sync(self.net_sync.talk(
@ -141,7 +141,7 @@ class Client(QObject):
self.roomLeft.emit(room_id)
@futurize
@futurize()
def loadPastEvents(self, room_id: str, start_token: str, limit: int = 100
) -> None:
# From QML, use Backend.loastPastEvents instead
@ -170,7 +170,7 @@ class Client(QObject):
@pyqtSlot(str, bool)
@futurize
@futurize(max_instances=1)
def setTypingState(self, room_id: str, typing: bool) -> None:
set_for_secs = 5
last_set, last_time = self._last_typing_set[room_id]
@ -183,6 +183,7 @@ class Client(QObject):
self._last_typing_set[room_id] = (typing, time.time())
print("send", typing)
self.net.talk(
self.nio.room_typing,
room_id = room_id,
@ -192,7 +193,7 @@ class Client(QObject):
@pyqtSlot(str, str)
@futurize
@futurize()
def sendMarkdown(self, room_id: str, text: str) -> None:
html = self.manager.backend.htmlFilter.fromMarkdown(text)
content = {

View File

@ -5,9 +5,8 @@ import functools
import logging
import sys
import traceback
from concurrent.futures import Future
from threading import currentThread
from typing import Callable, Optional, Union
from concurrent.futures import Executor, Future
from typing import Callable, List, Optional, Tuple, Union
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
@ -63,17 +62,33 @@ class PyQtFuture(QObject):
self.future.add_done_callback(fn)
def futurize(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(self, *args, **kwargs) -> PyQtFuture:
def run_and_catch_errs():
# Without this, exceptions are silently ignored
try:
return func(self, *args, **kwargs)
except Exception:
traceback.print_exc()
logging.error("Exiting %s due to exception.", currentThread())
sys.exit(1)
_RUNNING: List[Tuple[Executor, Callable, tuple, dict]] = []
return PyQtFuture(self.pool.submit(run_and_catch_errs), self)
return wrapper
def futurize(max_instances: Optional[int] = None) -> Callable:
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(self, *args, **kws) -> Optional[PyQtFuture]:
def run_and_catch_errs():
# Without this, exceptions are silently ignored
try:
return func(self, *args, **kws)
except Exception:
traceback.print_exc()
logging.error("Exiting thread due to exception.")
sys.exit(1)
finally:
del _RUNNING[_RUNNING.index((self.pool, func, args, kws))]
if max_instances is not None and \
_RUNNING.count((self.pool, func, args, kws)) >= max_instances:
return None
_RUNNING.append((self.pool, func, args, kws))
return PyQtFuture(self.pool.submit(run_and_catch_errs), self)
return wrapper
return decorator