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

Skip to content

Commit 01b89e5

Browse files
committed
BufferedChannel can be closed eagerly via callback
Fix a bug in streaming: - when server is done streaming, it send STREAM_DONE and close the channel (heartbeat is closed as well). - because client handle STREAM_DONE only when user's code pull data from the client side iterator, and if the user's code doest consume the iterator, then the heartbeat will be still active on the client, even if the stream is technically closed. The solution: - A callback is called before enqueuing events in BufferChannel. If this callback return True, the BufferChannel is closed. - This way, the client can close properly the BufferChannel and eventually consume the remaining queued events.
1 parent 72086db commit 01b89e5

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed

tests/test_client_heartbeat.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,102 @@ def slow(self):
9393

9494
assert client.slow() == 2
9595
print 'GOT ANSWER'
96+
97+
98+
def test_client_hb_doesnt_linger_on_streaming():
99+
endpoint = random_ipc_endpoint()
100+
101+
class MySrv(zerorpc.Server):
102+
103+
@zerorpc.stream
104+
def iter(self):
105+
return xrange(42)
106+
107+
srv = MySrv(heartbeat=1, context=zerorpc.Context())
108+
srv.bind(endpoint)
109+
gevent.spawn(srv.run)
110+
111+
client1 = zerorpc.Client(endpoint, heartbeat=1, context=zerorpc.Context())
112+
113+
def test_client():
114+
assert list(client1.iter()) == list(xrange(42))
115+
print 'sleep 3s'
116+
gevent.sleep(3)
117+
118+
gevent.spawn(test_client).join()
119+
120+
121+
def est_client_drop_few():
122+
endpoint = random_ipc_endpoint()
123+
124+
class MySrv(zerorpc.Server):
125+
126+
def lolita(self):
127+
return 42
128+
129+
srv = MySrv(heartbeat=1, context=zerorpc.Context())
130+
srv.bind(endpoint)
131+
gevent.spawn(srv.run)
132+
133+
client1 = zerorpc.Client(endpoint, heartbeat=1, context=zerorpc.Context())
134+
client2 = zerorpc.Client(endpoint, heartbeat=1, context=zerorpc.Context())
135+
client3 = zerorpc.Client(endpoint, heartbeat=1, context=zerorpc.Context())
136+
137+
assert client1.lolita() == 42
138+
assert client2.lolita() == 42
139+
140+
gevent.sleep(3)
141+
assert client3.lolita() == 42
142+
143+
144+
def test_client_drop_empty_stream():
145+
endpoint = random_ipc_endpoint()
146+
147+
class MySrv(zerorpc.Server):
148+
149+
@zerorpc.stream
150+
def iter(self):
151+
return []
152+
153+
srv = MySrv(heartbeat=1, context=zerorpc.Context())
154+
srv.bind(endpoint)
155+
gevent.spawn(srv.run)
156+
157+
client1 = zerorpc.Client(endpoint, heartbeat=1, context=zerorpc.Context())
158+
159+
def test_client():
160+
print 'grab iter'
161+
i = client1.iter()
162+
163+
print 'sleep 3s'
164+
gevent.sleep(3)
165+
166+
gevent.spawn(test_client).join()
167+
168+
169+
def test_client_drop_stream():
170+
endpoint = random_ipc_endpoint()
171+
172+
class MySrv(zerorpc.Server):
173+
174+
@zerorpc.stream
175+
def iter(self):
176+
return xrange(500)
177+
178+
srv = MySrv(heartbeat=1, context=zerorpc.Context())
179+
srv.bind(endpoint)
180+
gevent.spawn(srv.run)
181+
182+
client1 = zerorpc.Client(endpoint, heartbeat=1, context=zerorpc.Context())
183+
184+
def test_client():
185+
print 'grab iter'
186+
i = client1.iter()
187+
188+
print 'consume some'
189+
assert list(next(i) for x in xrange(142)) == list(xrange(142))
190+
191+
print 'sleep 3s'
192+
gevent.sleep(3)
193+
194+
gevent.spawn(test_client).join()

zerorpc/channel.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,21 @@ def __init__(self, channel, inqueue_size=100):
171171
self._input_queue = gevent.queue.Queue()
172172
self._lost_remote = False
173173
self._verbose = False
174+
self._on_close_if = None
174175
self._recv_task = gevent.spawn(self._recver)
175176

176177
@property
177178
def recv_is_available(self):
178179
return self._channel.recv_is_available
179180

181+
@property
182+
def on_close_if(self):
183+
return self._on_close_if
184+
185+
@on_close_if.setter
186+
def on_close_if(self, cb):
187+
self._on_close_if = cb
188+
180189
def __del__(self):
181190
self.close()
182191

@@ -205,6 +214,10 @@ def _recver(self):
205214
'BufferedChannel, queue overflow on event:', event)
206215
else:
207216
self._input_queue.put(event)
217+
if self._on_close_if is not None and self._on_close_if(event):
218+
self._recv_task = None
219+
self.close()
220+
return
208221

209222
def create_event(self, name, args, xheader={}):
210223
return self._channel.create_event(name, args, xheader)

zerorpc/patterns.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,19 @@ def accept_answer(self, event):
5656

5757
def process_answer(self, context, bufchan, event, method,
5858
raise_remote_error):
59+
60+
def is_stream_done(event):
61+
return event.name == 'STREAM_DONE'
62+
bufchan.on_close_if = is_stream_done
63+
5964
def iterator(event):
6065
while event.name == 'STREAM':
6166
yield event.args
6267
event = bufchan.recv()
6368
if event.name == 'ERR':
6469
raise_remote_error(event)
6570
bufchan.close()
71+
6672
return iterator(event)
6773

6874

0 commit comments

Comments
 (0)