Skip to content

Commit 5b848cb

Browse files
committed
Multiple resource leaks fixed in proxy and dealer
1 parent c1a7836 commit 5b848cb

File tree

10 files changed

+300
-125
lines changed

10 files changed

+300
-125
lines changed

crossbar/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
if not hasattr(eth_abi, 'encode_single') and hasattr(eth_abi, 'encode'):
2323
eth_abi.encode_single = eth_abi.encode
2424

25+
import eth_typing
26+
if not hasattr(eth_typing, 'ChainID') and hasattr(eth_typing, 'ChainId'):
27+
eth_typing.ChainID = eth_typing.ChainId
28+
2529
# monkey patch web3 for master branch / upcoming v6 (which we need for python 3.11)
2630
# AttributeError: type object 'Web3' has no attribute 'toChecksumAddress'. Did you mean: 'to_checksum_address'?
2731
import web3

crossbar/router/dealer.py

+45-15
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class InvocationRequest(object):
4040
'id',
4141
'registration',
4242
'caller',
43+
'caller_session_id',
4344
'call',
4445
'callee',
4546
'forward_for',
@@ -53,6 +54,7 @@ def __init__(self, id, registration, caller, call, callee, forward_for, authoriz
5354
self.id = id
5455
self.registration = registration
5556
self.caller = caller
57+
self.caller_session_id = caller._session_id
5658
self.call = call
5759
self.callee = callee
5860
self.forward_for = forward_for
@@ -185,6 +187,7 @@ def detach(self, session):
185187
is_rlink_session = (session._authrole == "rlink")
186188
if session in self._caller_to_invocations:
187189

190+
# this needs to update all four places where we track invocations similar to _remove_invoke_request
188191
outstanding = self._caller_to_invocations.get(session, [])
189192
for invoke in outstanding: # type: InvocationRequest
190193
if invoke.canceled:
@@ -207,11 +210,26 @@ def detach(self, session):
207210
request=invoke.id,
208211
session=session._session_id,
209212
)
213+
214+
if invoke.timeout_call:
215+
invoke.timeout_call.cancel()
216+
invoke.timeout_call = None
217+
218+
invokes = self._callee_to_invocations[callee]
219+
invokes.remove(invoke)
220+
if not invokes:
221+
del self._callee_to_invocations[callee]
222+
223+
del self._invocations[invoke.id]
224+
del self._invocations_by_call[(invoke.caller_session_id, invoke.call.request)]
225+
210226
self._router.send(invoke.callee, message.Interrupt(
211227
request=invoke.id,
212228
mode=message.Cancel.KILLNOWAIT,
213229
))
214230

231+
del self._caller_to_invocations[session]
232+
215233
if session in self._session_to_registrations:
216234

217235
# send out Errors for any in-flight calls we have
@@ -235,6 +253,21 @@ def detach(self, session):
235253
if invoke.caller._transport:
236254
invoke.caller._transport.send(reply)
237255

256+
if invoke.timeout_call:
257+
invoke.timeout_call.cancel()
258+
invoke.timeout_call = None
259+
260+
invokes = self._caller_to_invocations[invoke.caller]
261+
invokes.remove(invoke)
262+
if not invokes:
263+
del self._caller_to_invocations[invoke.caller]
264+
265+
del self._invocations[invoke.id]
266+
del self._invocations_by_call[(invoke.caller_session_id, invoke.call.request)]
267+
268+
if outstanding:
269+
del self._callee_to_invocations[session]
270+
238271
for registration in self._session_to_registrations[session]:
239272
was_registered, was_last_callee = self._registration_map.drop_observer(session, registration)
240273

@@ -1120,23 +1153,20 @@ def _remove_invoke_request(self, invocation_request):
11201153
invocation_request.timeout_call.cancel()
11211154
invocation_request.timeout_call = None
11221155

1123-
invokes = self._callee_to_invocations[invocation_request.callee]
1124-
invokes.remove(invocation_request)
1125-
if not invokes:
1126-
del self._callee_to_invocations[invocation_request.callee]
1127-
1128-
invokes = self._caller_to_invocations[invocation_request.caller]
1129-
invokes.remove(invocation_request)
1130-
if not invokes:
1131-
del self._caller_to_invocations[invocation_request.caller]
1156+
# all four places should always be updated together
1157+
if invocation_request.id in self._invocations:
1158+
del self._invocations[invocation_request.id]
1159+
invokes = self._callee_to_invocations[invocation_request.callee]
1160+
invokes.remove(invocation_request)
1161+
if not invokes:
1162+
del self._callee_to_invocations[invocation_request.callee]
11321163

1133-
del self._invocations[invocation_request.id]
1164+
invokes = self._caller_to_invocations[invocation_request.caller]
1165+
invokes.remove(invocation_request)
1166+
if not invokes:
1167+
del self._caller_to_invocations[invocation_request.caller]
11341168

1135-
# the session_id will be None if the caller session has
1136-
# already vanished
1137-
caller_id = invocation_request.caller._session_id
1138-
if caller_id is not None:
1139-
del self._invocations_by_call[caller_id, invocation_request.call.request]
1169+
del self._invocations_by_call[invocation_request.caller_session_id, invocation_request.call.request]
11401170

11411171
# noinspection PyUnusedLocal
11421172
def processCancel(self, session, cancel):

crossbar/router/session.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -736,12 +736,13 @@ def onClose(self, wasClean):
736736
self.onLeave(CloseDetails())
737737
except Exception:
738738
self.log.failure("Exception raised in onLeave callback")
739+
self.log.warn("{tb}".format(tb=Failure().getTraceback()))
739740

740741
try:
741742
self._router.detach(self)
742743
except Exception as e:
743744
self.log.error("Failed to detach session '{}': {}".format(self._session_id, e))
744-
self.log.debug("{tb}".format(tb=Failure().getTraceback()))
745+
self.log.warn("{tb}".format(tb=Failure().getTraceback()))
745746

746747
self._session_id = None
747748

crossbar/router/test/test_dealer.py

+7
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,17 @@ def test_outstanding_invoke_but_caller_gone(self):
111111
outstanding = mock.Mock()
112112
outstanding.call.request = 1
113113

114+
# there was a bug where timeout calls were not getting cancelled
115+
# mock has non-null timeout_call, so we need to set it to None
116+
outstanding.timeout_call = None
114117
dealer = self.router._dealer
115118
dealer.attach(session)
116119

120+
# All four maps involved in invocation tracking must be updated atomically
121+
dealer._caller_to_invocations[outstanding.caller] = [outstanding]
117122
dealer._callee_to_invocations[session] = [outstanding]
123+
dealer._invocations[outstanding.id] = outstanding
124+
dealer._invocations_by_call[(outstanding.caller_session_id, outstanding.call.request)] = outstanding
118125
# pretend we've disconnected already
119126
outstanding.caller._transport = None
120127

crossbar/worker/main.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def _run_command_exec_worker(options, reactor=None, personality=None):
123123

124124
# we use an Autobahn utility to import the "best" available Twisted reactor
125125
from autobahn.twisted.choosereactor import install_reactor
126-
reactor = install_reactor(options.reactor)
126+
reactor = install_reactor(explicit_reactor=options.reactor or os.environ.get('CROSSBAR_REACTOR', None))
127127

128128
# make sure logging to something else than stdio is setup _first_
129129
from crossbar._logging import make_JSON_observer, cb_logging_aware

0 commit comments

Comments
 (0)