Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions nextsync5.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,18 @@ def _is_osp(payload):
payload[1:1 + len(OSP_MARK)] == OSP_MARK)


def _goodbye_linger(conn):
"""Drain until the peer closes (bounded 2 s) after sending a goodbye,
so our FIN cannot race the 'Q' off the far side's wire - see
_re_goodbye_linger in zxnu_workers.py, kept in step by hand."""
try:
conn.settimeout(2.0)
while conn.recv(256):
pass
except OSError:
pass


def fail_block_payload(data):
"""The verified payload of a framed 'F' status block, else None.

Expand Down Expand Up @@ -881,13 +893,15 @@ def _listen_session_inner(conn, stats, _test_commands=None):
reply = item[3] if len(item) > 3 else None
if op == "quit":
sendpacket(conn, b"Q", 0)
_goodbye_linger(conn)
print(f'{timestamp()} | listen: sent quit')
_reply_fill(reply, {'ok': True})
break
elif op == "quit_app":
# /forceexit: leave listen mode AND end the far application
# (ZX Next Remote 0.9.47+; a dot exits to BASIC either way).
sendpacket(conn, b"Q" + QUIT_EXIT_MARK, 0)
_goodbye_linger(conn)
print(f'{timestamp()} | listen: sent quit (exit application)')
_reply_fill(reply, {'ok': True})
break
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ build-backend = "setuptools.build_meta"
name = "zx-next-unite"
# MUST match ZX_NEXT_UNITE_VERSION in zxnu_config.py — the release workflow
# gates on it (same rule as the git tag).
version = "9.5.25"
version = "9.5.26"
description = "GUI toolbox for the ZX Spectrum Next: SD-card/HDF image explorer, NextSync Wi-Fi file sync + Remote Explorer & HTTP bridge, and built-in software browsing via GetIt, ZXDB and zxArt"
readme = "README.md"
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion zxnu_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from PySide6.QtGui import QColor


ZX_NEXT_UNITE_VERSION = "9.5.25"
ZX_NEXT_UNITE_VERSION = "9.5.26"
# Version of the bundled NextSync .sync5 dotN command (nextsync/sync/server/
# dot/syncdev, also attached to GitHub releases as the "sync5" asset). MUST be
# kept in sync with the banner in nextsync/sync/z88dk/nextsync.c ("NextSync
Expand Down
23 changes: 23 additions & 0 deletions zxnu_workers.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,27 @@ def _re_sendpacket(conn, payload, pktno):
bytes([c0, c1, pktno & 0xff]))


def _re_goodbye_linger(conn):
"""Give a just-sent goodbye ('Q' / the marked 'Q'+exit) time to be READ
before our FIN can race it off the peer's wire.

The far side of a -listen session reads over an ESP UART, where the
frame and the close can arrive in one breath - and ZXNextRemote before
0.9.52 examined the close FIRST, so quitting a session threw the very
goodbye away unread (the /forceexit-does-nothing field report; the
dot's loop reads first and never showed it). Draining until the peer
closes - bounded at 2 s - means the FIN only ever follows the goodbye,
which fixes every field build without asking anyone to update. The dot
closes within milliseconds of reading 'Q', so the wait is invisible
there."""
try:
conn.settimeout(2.0)
while conn.recv(256):
pass # stray polls: consumed, not answered
except OSError:
pass # timeout or reset: we tried, close


def _re_recv_exact(conn, n):
buf = b''
while len(buf) < n:
Expand Down Expand Up @@ -959,11 +980,13 @@ def _put_finish(ok, osp=False):
# everyone, quitting somebody's app is aimed at the
# machine the caller targeted and nobody else.
_re_sendpacket(conn, b"Q" + RE_QUIT_EXIT_MARK, 0)
_re_goodbye_linger(conn)
if reply is not None:
reply.put({'ok': True})
break
if op == "quit":
_re_sendpacket(conn, b"Q", 0)
_re_goodbye_linger(conn)
# Stop is for EVERYONE: tell every OTHER
# session's Next to leave at its next poll too.
with plock:
Expand Down