From dbdb8d5a595074de4c2bdf055a3bd6ba6cdec013 Mon Sep 17 00:00:00 2001 From: Kent Bull Date: Thu, 27 Aug 2026 01:38:23 -0600 Subject: [PATCH 1/3] Reset Remoter idle timeout from current tyme Use Tymer.start() so successful I/O refreshes inactivity from the current scheduler tyme instead of accumulating duration from the previous stop. Cover repeated refreshes and recovery after expiration. --- src/hio/core/tcp/serving.py | 4 ++-- tests/core/tcp/test_tcp.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/hio/core/tcp/serving.py b/src/hio/core/tcp/serving.py index d69256a4..e734346b 100644 --- a/src/hio/core/tcp/serving.py +++ b/src/hio/core/tcp/serving.py @@ -690,9 +690,9 @@ def close(self): def refresh(self): """ - Restart tymer + Reset idle tymer from current tyme. """ - self.tymer.restart() + self.tymer.start() def receive(self): diff --git a/tests/core/tcp/test_tcp.py b/tests/core/tcp/test_tcp.py index 19890c6e..a271f84c 100644 --- a/tests/core/tcp/test_tcp.py +++ b/tests/core/tcp/test_tcp.py @@ -16,6 +16,43 @@ from hio.base import tyming, doing from hio.core import tcp + +def test_remoter_refresh_resets_idle_tymeout(): + """Test Remoter activity resets its idle tymer from current tyme.""" + tymist = tyming.Tymist() + remoter = tcp.Remoter( + tymth=tymist.tymen(), + ha=("127.0.0.1", 56000), + ca=("127.0.0.1", 56001), + cs=None, + tymeout=1.0, + ) + + assert remoter.tymer.duration == 1.0 + assert remoter.tymer.remaining == 1.0 + + tymist.tick(tock=0.75) + assert remoter.tymer.remaining == 0.25 + + remoter.refresh() + assert remoter.tymer.duration == 1.0 + assert remoter.tymer.remaining == pytest.approx(1.0) + + # Multiple activity notifications at the same tyme must not accumulate + # additional idle-time credit. + remoter.refresh() + assert remoter.tymer.remaining == pytest.approx(1.0) + + tymist.tick(tock=1.25) + assert remoter.tymer.expired + + # Activity after expiration starts a new full idle interval. + remoter.refresh() + assert not remoter.tymer.expired + assert remoter.tymer.duration == 1.0 + assert remoter.tymer.remaining == pytest.approx(1.0) + + def test_tcp_basic(): """ Test the tcp connection between client and server From 56ba95333325c9cfcffb22a233388aee4cb8b123 Mon Sep 17 00:00:00 2001 From: Kent Bull Date: Thu, 27 Aug 2026 01:42:52 -0600 Subject: [PATCH 2/3] Preserve refresh without a tyme base Skip idle timer reset until a Remoter has an injected scheduler tyme. This preserves untimed TCP and HTTP use while Tymer.start() governs configured inactivity. --- src/hio/core/tcp/serving.py | 3 ++- tests/core/tcp/test_tcp.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/hio/core/tcp/serving.py b/src/hio/core/tcp/serving.py index e734346b..2e3d6e8a 100644 --- a/src/hio/core/tcp/serving.py +++ b/src/hio/core/tcp/serving.py @@ -692,7 +692,8 @@ def refresh(self): """ Reset idle tymer from current tyme. """ - self.tymer.start() + if self.tymer.tyme is not None: + self.tymer.start() def receive(self): diff --git a/tests/core/tcp/test_tcp.py b/tests/core/tcp/test_tcp.py index a271f84c..56df3831 100644 --- a/tests/core/tcp/test_tcp.py +++ b/tests/core/tcp/test_tcp.py @@ -52,6 +52,11 @@ def test_remoter_refresh_resets_idle_tymeout(): assert remoter.tymer.duration == 1.0 assert remoter.tymer.remaining == pytest.approx(1.0) + # Remoters without an injected tyme base have no idle time to refresh. + unwound = tcp.Remoter(ha=remoter.ha, ca=remoter.ca, cs=None) + unwound.refresh() + assert unwound.tymer.duration == 0.0 + def test_tcp_basic(): """ From 2f44368a2c90f1b8f56d63ed35e0e2b28fbeb997 Mon Sep 17 00:00:00 2001 From: Kent Bull Date: Thu, 27 Aug 2026 02:12:39 -0600 Subject: [PATCH 3/3] Refresh TLS idle timeout after successful I/O RemoterTls overrides the plain TCP receive and send paths but omitted their activity refresh. Mirror Remoter so nonempty reads and nonzero writes reset configured inactivity without refreshing blocked or failed I/O. Extend the existing TLS integration test to verify both directions. This fixes #170 and relies on the current-tyme refresh semantics introduced earlier in this branch. --- src/hio/core/tcp/serving.py | 6 ++++++ tests/core/tcp/test_tcp.py | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/hio/core/tcp/serving.py b/src/hio/core/tcp/serving.py index 2e3d6e8a..934a9b91 100644 --- a/src/hio/core/tcp/serving.py +++ b/src/hio/core/tcp/serving.py @@ -966,6 +966,9 @@ def receive(self): if self.wl: # log over the wire rx self.wl.writeRx(data, who=self.cs.getpeername()) + if self.refreshable: + self.refresh() + else: # data empty so connection closed on other end self.cutoff = True @@ -1004,6 +1007,9 @@ def send(self, data): if self.wl: self.wl.writeTx(data[:result], who=self.cs.getpeername()) + if self.refreshable: + self.refresh() + return result diff --git a/tests/core/tcp/test_tcp.py b/tests/core/tcp/test_tcp.py index 56df3831..975148b9 100644 --- a/tests/core/tcp/test_tcp.py +++ b/tests/core/tcp/test_tcp.py @@ -773,6 +773,11 @@ def test_tcp_tls_default_context(): assert ixBeta.ca == beta.ca assert ixBeta.ha == beta.ha + ixBeta.tymeout = 1.0 + ixBeta.tymer.start(duration=ixBeta.tymeout) + tymist.tick(tock=0.75) + assert ixBeta.tymer.remaining == pytest.approx(0.25) + msgOut = b"Beta sends to Server\n" beta.tx(msgOut) while not( not beta.txbs and ixBeta.rxbs): @@ -785,8 +790,12 @@ def test_tcp_tls_default_context(): msgIn = bytes(ixBeta.rxbs) assert msgIn == msgOut + assert ixBeta.tymer.remaining == pytest.approx(1.0) ixBeta.clearRxbs() + tymist.tick(tock=0.75) + assert ixBeta.tymer.remaining == pytest.approx(0.25) + msgOut = b'Server sends to Beta\n' ixBeta.tx(msgOut) while not (not ixBeta.txbs and beta.rxbs): @@ -796,6 +805,7 @@ def test_tcp_tls_default_context(): msgIn = bytes(beta.rxbs) assert msgIn == msgOut + assert ixBeta.tymer.remaining == pytest.approx(1.0) beta.clearRxbs() assert beta.opened == False