Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5c66bf0

Browse files
committed
Fix a really stupid and old bug.
- server side, you accept a new channel - you stack an heartbeatonchannel on top - you slow everything enough so the client already sent some heartbeat msgs before you could read the first msg (cpu intensive task, time.sleep...) - you send back the response - you close the heartbeatonchannel - this kills the coroutine handling the heartbeat - this action eventually switch to the hearbeat coroutine to raise GreenletExit in it. - which after might ends up to the recver coroutine (not yet killed) - the recever coroutine gets the heartbeat msg from the client - and stupidly process to spawn a new heartbeat coroutine! - this eventually switch back to the close() - which set the self._heartbeat to None (so potentially another heartbeat msg could spawn another heartbeat coroutine!) - after enough back and forth, self._channel is set to None - one or more of the heartbeat coroutines is trying to access self._channel.emit... and BOOM, you get (an harmless) stacktrace in the logs. The solution is to set a flag when close() is called: self._close = True. _start_hearbeat() called from the recver coroutine can then check this flag. Last but not least, the heartbeat coroutine checks this flag before killing the parent coroutine. This is not strictly necessary, but if in the future the code in close() eventually yields back to gevent (for example, by trying to kill another coroutine before the heartbeat's one), this will prevent a LostRemote to be raised on close(). And finally, a forgotten variable was removed (in channel.py).
1 parent 8c612a2 commit 5c66bf0

File tree

2 files changed

+6
-4
lines changed

2 files changed

+6
-4
lines changed

zerorpc/channel.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ def __init__(self, channel, inqueue_size=100):
171171
self._input_queue_reserved = 1
172172
self._remote_can_recv = gevent.event.Event()
173173
self._input_queue = gevent.queue.Queue()
174-
self._lost_remote = False
175174
self._verbose = False
176175
self._on_close_if = None
177176
self._recv_task = gevent.spawn(self._recver)

zerorpc/heartbeat.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
class HeartBeatOnChannel(ChannelBase):
3838

3939
def __init__(self, channel, freq=5, passive=False):
40+
self._closed = False
4041
self._channel = channel
4142
self._heartbeat_freq = freq
4243
self._input_queue = gevent.queue.Channel()
@@ -58,6 +59,7 @@ def emit_is_supported(self):
5859
return self._channel.emit_is_supported
5960

6061
def close(self):
62+
self._closed = True
6163
if self._heartbeat_task is not None:
6264
self._heartbeat_task.kill()
6365
self._heartbeat_task = None
@@ -75,13 +77,14 @@ def _heartbeat(self):
7577
self._remote_last_hb = time.time()
7678
if time.time() > self._remote_last_hb + self._heartbeat_freq * 2:
7779
self._lost_remote = True
78-
gevent.kill(self._parent_coroutine,
79-
self._lost_remote_exception())
80+
if not self._closed:
81+
gevent.kill(self._parent_coroutine,
82+
self._lost_remote_exception())
8083
break
8184
self._channel.emit('_zpc_hb', (0,)) # 0 -> compat with protocol v2
8285

8386
def _start_heartbeat(self):
84-
if self._heartbeat_task is None and self._heartbeat_freq is not None:
87+
if self._heartbeat_task is None and self._heartbeat_freq is not None and not self._closed:
8588
self._heartbeat_task = gevent.spawn(self._heartbeat)
8689

8790
def _recver(self):

0 commit comments

Comments
 (0)