Handle network errors
- Move HTTP connect/disconnect logic to networkManager - If a talk fails due to socket error, HTTP transport error or nio bad response that might change, retry every 2s until success - Clean up some leftover debug prints
This commit is contained in:
parent
1f04fa07cb
commit
0d7728665f
2
TODO.md
2
TODO.md
|
@ -7,8 +7,6 @@
|
||||||
it should be the peer's display name instead.
|
it should be the peer's display name instead.
|
||||||
- Support "Empty room (was ...)" after peer left
|
- Support "Empty room (was ...)" after peer left
|
||||||
|
|
||||||
- Catch network errors in socket operations
|
|
||||||
|
|
||||||
- Proper logoff when closing client
|
- Proper logoff when closing client
|
||||||
|
|
||||||
- Handle cases where an avatar char is # or @ (#alias room, @user\_id)
|
- Handle cases where an avatar char is # or @ (#alias room, @user\_id)
|
||||||
|
|
|
@ -79,10 +79,7 @@ class Client(QObject):
|
||||||
@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:
|
||||||
self.net.write(self.nio.connect())
|
|
||||||
response = self.net.talk(self.nio.login, password, device_name)
|
response = self.net.talk(self.nio.login, password, device_name)
|
||||||
|
|
||||||
self.net_sync.write(self.nio_sync.connect())
|
|
||||||
self.nio_sync.receive_response(response)
|
self.nio_sync.receive_response(response)
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,11 +87,8 @@ class Client(QObject):
|
||||||
@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:
|
||||||
self.net.write(self.nio.connect())
|
|
||||||
response = nr.LoginResponse(user_id, device_id, token)
|
response = nr.LoginResponse(user_id, device_id, token)
|
||||||
self.nio.receive_response(response)
|
self.nio.receive_response(response)
|
||||||
|
|
||||||
self.net_sync.write(self.nio_sync.connect())
|
|
||||||
self.nio_sync.receive_response(response)
|
self.nio_sync.receive_response(response)
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,8 +96,8 @@ class Client(QObject):
|
||||||
@futurize
|
@futurize
|
||||||
def logout(self) -> None:
|
def logout(self) -> None:
|
||||||
self._stop_sync.set()
|
self._stop_sync.set()
|
||||||
self.net.write(self.nio.disconnect())
|
self.net.http_disconnect()
|
||||||
self.net_sync.write(self.nio_sync.disconnect())
|
self.net_sync.http_disconnect()
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
|
@ -181,24 +175,19 @@ class Client(QObject):
|
||||||
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]
|
||||||
|
|
||||||
print(last_set, last_time)
|
|
||||||
|
|
||||||
if not typing and last_set is False:
|
if not typing and last_set is False:
|
||||||
print("ignore 1")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if typing and time.time() - last_time < set_for_secs - 1:
|
if typing and time.time() - last_time < set_for_secs - 1:
|
||||||
print("ignore 2")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
print("SET", typing)
|
|
||||||
self._last_typing_set[room_id] = (typing, time.time())
|
self._last_typing_set[room_id] = (typing, time.time())
|
||||||
|
|
||||||
self.net.talk(
|
self.net.talk(
|
||||||
self.nio.room_typing,
|
self.nio.room_typing,
|
||||||
room_id = room_id,
|
room_id = room_id,
|
||||||
typing_state = typing,
|
typing_state = typing,
|
||||||
timeout = set_for_secs * 1000,
|
timeout = set_for_secs * 1000,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import html_sanitizer.sanitizer as sanitizer
|
|
||||||
import mistune
|
import mistune
|
||||||
from lxml.html import HtmlElement, etree
|
from lxml.html import HtmlElement, etree
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSlot
|
||||||
|
|
||||||
|
import html_sanitizer.sanitizer as sanitizer
|
||||||
|
|
||||||
|
|
||||||
class HtmlFilter(QObject):
|
class HtmlFilter(QObject):
|
||||||
link_regexes = [re.compile(r, re.IGNORECASE) for r in [
|
link_regexes = [re.compile(r, re.IGNORECASE) for r in [
|
||||||
|
|
|
@ -11,6 +11,7 @@ from uuid import UUID
|
||||||
|
|
||||||
import nio
|
import nio
|
||||||
import nio.responses as nr
|
import nio.responses as nr
|
||||||
|
from nio.exceptions import RemoteTransportError
|
||||||
|
|
||||||
OptSock = Optional[ssl.SSLSocket]
|
OptSock = Optional[ssl.SSLSocket]
|
||||||
NioRequestFunc = Callable[..., Tuple[UUID, bytes]]
|
NioRequestFunc = Callable[..., Tuple[UUID, bytes]]
|
||||||
|
@ -48,7 +49,10 @@ class NetworkManager:
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _close_socket(sock: socket.socket) -> None:
|
def _close_socket(sock: Optional[socket.socket]) -> None:
|
||||||
|
if not sock:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sock.shutdown(how=socket.SHUT_RDWR)
|
sock.shutdown(how=socket.SHUT_RDWR)
|
||||||
except OSError: # Already closer by server
|
except OSError: # Already closer by server
|
||||||
|
@ -56,6 +60,14 @@ class NetworkManager:
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
|
|
||||||
|
def http_disconnect(self) -> None:
|
||||||
|
data = self.nio.disconnect()
|
||||||
|
try:
|
||||||
|
self.write(data)
|
||||||
|
except (OSError, RemoteTransportError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def read(self, with_sock: OptSock = None) -> nr.Response:
|
def read(self, with_sock: OptSock = None) -> nr.Response:
|
||||||
sock = with_sock or self._get_socket()
|
sock = with_sock or self._get_socket()
|
||||||
|
|
||||||
|
@ -78,9 +90,6 @@ class NetworkManager:
|
||||||
|
|
||||||
|
|
||||||
def write(self, data: bytes, with_sock: OptSock = None) -> None:
|
def write(self, data: bytes, with_sock: OptSock = None) -> None:
|
||||||
if not data:
|
|
||||||
return
|
|
||||||
|
|
||||||
sock = with_sock or self._get_socket()
|
sock = with_sock or self._get_socket()
|
||||||
sock.sendall(data)
|
sock.sendall(data)
|
||||||
|
|
||||||
|
@ -88,34 +97,47 @@ class NetworkManager:
|
||||||
self._close_socket(sock)
|
self._close_socket(sock)
|
||||||
|
|
||||||
|
|
||||||
def talk(self, nio_func: NioRequestFunc, *args, **kwargs) -> nr.Response:
|
def talk(self,
|
||||||
|
nio_func: NioRequestFunc,
|
||||||
|
*args,
|
||||||
|
**kwargs) -> nr.Response:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
while True:
|
while True:
|
||||||
to_send = nio_func(*args, **kwargs)[1]
|
sock = None
|
||||||
sock = self._get_socket()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
sock = self._get_socket()
|
||||||
|
|
||||||
|
if not self.nio.connection:
|
||||||
|
# Establish HTTP protocol connection:
|
||||||
|
self.write(self.nio.connect(), sock)
|
||||||
|
|
||||||
|
to_send = nio_func(*args, **kwargs)[1]
|
||||||
self.write(to_send, sock)
|
self.write(to_send, sock)
|
||||||
response = self.read(sock)
|
response = self.read(sock)
|
||||||
|
|
||||||
|
except OSError as err:
|
||||||
|
logging.error("Socket error for %s: %s",
|
||||||
|
nio_func.__name__, err.strerror)
|
||||||
|
self._close_socket(sock)
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
except RemoteTransportError as err:
|
||||||
|
logging.error("HTTP transport error for %s: %s",
|
||||||
|
nio_func.__name__, err)
|
||||||
|
self._close_socket(sock)
|
||||||
|
self.http_disconnect()
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
except NioErrorResponse as err:
|
except NioErrorResponse as err:
|
||||||
logging.error("bad read for %s: %s", nio_func, err)
|
logging.error("Nio response error for %s: %s",
|
||||||
|
nio_func.__name__, err)
|
||||||
self._close_socket(sock)
|
self._close_socket(sock)
|
||||||
|
|
||||||
if self._should_abort_talk(err):
|
if err.response.status_code in self.http_retry_codes:
|
||||||
logging.error("aborting talk")
|
return response
|
||||||
break
|
|
||||||
|
|
||||||
time.sleep(10)
|
time.sleep(2)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
break
|
return response
|
||||||
|
|
||||||
self._close_socket(sock)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def _should_abort_talk(self, err: NioErrorResponse) -> bool:
|
|
||||||
if err.response.status_code in self.http_retry_codes:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
|
@ -62,7 +62,6 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textArea.text === "") { return }
|
if (textArea.text === "") { return }
|
||||||
|
|
||||||
Backend.clientManager.clients[chatPage.user_id]
|
Backend.clientManager.clients[chatPage.user_id]
|
||||||
.sendMarkdown(chatPage.room.room_id, textArea.text)
|
.sendMarkdown(chatPage.room.room_id, textArea.text)
|
||||||
textArea.clear()
|
textArea.clear()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user