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, result="QVariant")
@pyqtSlot(str, bool, result="QVariant") @pyqtSlot(str, bool, result="QVariant")
@futurize @futurize()
def getUserDisplayName(self, user_id: str, can_block: bool = True) -> str: def getUserDisplayName(self, user_id: str, can_block: bool = True) -> str:
if user_id in self._queried_displaynames: if user_id in self._queried_displaynames:
return self._queried_displaynames[user_id] return self._queried_displaynames[user_id]

View File

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

View File

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