Skip to content
Open
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: 12 additions & 2 deletions kiteconnect/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def onConnect(self, response): # noqa
# Overide method
def onOpen(self): # noqa
"""Called when the initial WebSocket opening handshake was completed."""
# init last pong time
self._last_pong_time = time.time()
# send ping
self._loop_ping()
# init last pong check after X seconds
Expand Down Expand Up @@ -83,10 +85,10 @@ def onClose(self, was_clean, code, reason): # noqa
self._last_ping_time = None
self._last_pong_time = None

if self._next_ping:
if self._next_ping and self._next_ping.active():
self._next_ping.cancel()

if self._next_pong_check:
if self._next_pong_check and self._next_pong_check.active():
self._next_pong_check.cancel()

def onPong(self, response): # noqa
Expand All @@ -109,6 +111,13 @@ def _loop_ping(self): # noqa
if self._last_ping_time:
log.debug("last ping was {} seconds back.".format(time.time() - self._last_ping_time))

if self.state == self.STATE_OPEN:
try:
self.sendPing()
except Exception as e:
if self.factory.debug:
log.debug("Error sending ping: {}".format(e))

# Set current time as last ping time
self._last_ping_time = time.time()

Expand All @@ -130,6 +139,7 @@ def _loop_pong_check(self):
last_pong_diff))
# drop existing connection to avoid ghost connection
self.dropConnection(abort=True)
return

# Call self after X seconds
self._next_pong_check = self.factory.reactor.callLater(self.PING_INTERVAL, self._loop_pong_check)
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/test_ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,35 @@ def test_sendMessage(self, protocol):
assert protocol.state == protocol.STATE_OPEN

protocol.sendMessage(six.b(json.dumps({"message": "blah"})))

def test_loop_ping_sends_ping(self, protocol):
protocol.sendPing = Mock()
protocol._loop_ping()

assert protocol.sendPing.called
assert protocol._last_ping_time is not None

def test_loop_pong_check_drops_on_timeout(self, protocol):
import time
protocol.dropConnection = Mock()
# Simulate last pong received 10 seconds ago (exceeding 2 * PING_INTERVAL = 5.0s)
protocol._last_pong_time = time.time() - 10
protocol._loop_pong_check()

assert protocol.dropConnection.called

def test_loop_pong_check_healthy(self, protocol):
import time
protocol.dropConnection = Mock()
# Simulate recent pong (healthy)
protocol._last_pong_time = time.time() - 1
protocol._loop_pong_check()

assert not protocol.dropConnection.called

def test_onOpen_initializes_ping_and_pong(self, protocol):
protocol.sendPing = Mock()
protocol.onOpen()

assert protocol._last_pong_time is not None
assert protocol.sendPing.called