Skip to content

Commit 4782db7

Browse files
authored
Merge pull request #2171 from jomuel/revisit-gevent-exception-handling
Revisit gevent exception handling
2 parents ea20cff + 041c476 commit 4782db7

File tree

19 files changed

+36
-294
lines changed

19 files changed

+36
-294
lines changed

CONTRIBUTING.md

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -428,25 +428,6 @@ def foo(a: typing.Optional[int] = None)
428428
```
429429

430430

431-
432-
##### Particuliarities
433-
434-
**Concurrency/Usage of gevent**
435-
436-
Raiden uses a patched version of gevent for concurrency.
437-
438-
The patching is necessary to enforce our security policy: Unhandled exceptions that occur inside a
439-
`gevent.Greenlet` are by default not propagated outside of it. However, we require Raiden to halt
440-
at every unhandled exception. To achieve this, the internal error handler of gevent is patched
441-
and the `Greenlet` class replaced with a customized `RaidenGreenlet` throughout the project.
442-
443-
**The methods `link`, `link_exception`, `link_value` and `rawlink` of the `Greenlet` class are
444-
broken by the patching and must never be used.** For example, an exception handler linked to a
445-
greenlet by `link_exception` will not be called; the exception will instead be bubbled up and
446-
cause Raiden to crash. `RaidenGreenlet` provides the equivalent methods `link_safe`,
447-
`link_exception_safe` and `rawlink_safe` to be used instead.
448-
449-
450431
#### Solidity
451432

452433
For solidity we generally follow the style guide as shown in the [solidity

raiden/app.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,11 @@
2626
INITIAL_PORT,
2727
)
2828
from raiden.utils import pex
29-
from raiden.utils.gevent_utils import configure_gevent
3029
from raiden.utils import typing
3130

3231

3332
log = structlog.get_logger(__name__)
3433

35-
configure_gevent()
36-
3734

3835
class App: # pylint: disable=too-few-public-methods
3936
DEFAULT_CONFIG = {

raiden/connection_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from binascii import unhexlify
22

33
import gevent
4+
from gevent.event import AsyncResult
45
from gevent.lock import Semaphore
56
from random import shuffle
67

@@ -18,7 +19,6 @@
1819
)
1920
from raiden.transfer import views
2021
from raiden.utils.typing import Address
21-
from raiden.utils.gevent_utils import RaidenAsyncResult
2222

2323
log = structlog.get_logger(__name__) # pylint: disable=invalid-name
2424

@@ -147,8 +147,8 @@ def connect(
147147

148148
def leave_async(self):
149149
""" Async version of `leave()` """
150-
leave_result = RaidenAsyncResult()
151-
gevent.spawn(self.leave).link_safe(leave_result)
150+
leave_result = AsyncResult()
151+
gevent.spawn(self.leave).link(leave_result)
152152
return leave_result
153153

154154
def leave(self, registry_address):

raiden/exceptions.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,3 @@ class ChannelOutdatedError(RaidenError):
191191
class InsufficientGasReserve(RaidenError):
192192
""" Raised when an action cannot be done because the available balance
193193
is not sufficient for the lifecycles of all active channels. """
194-
195-
196-
class UnhandledExceptionInGreenlet(RaidenError):
197-
"""Raised when a Greenlet has been linked but the
198-
linked functions failed to handle an exception."""
199-
200-
def __init__(self, exception):
201-
super().__init__(f'{type(exception)} ({str(exception) or "-"})')

raiden/network/proxies/secret_registry.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import structlog
2+
from gevent.event import AsyncResult
23
from typing import List
34

45
from web3.exceptions import BadFunctionCallOutput
@@ -34,7 +35,6 @@
3435
privatekey_to_address,
3536
compare_versions,
3637
)
37-
from raiden.utils.gevent_utils import RaidenAsyncResult
3838

3939
log = structlog.get_logger(__name__) # pylint: disable=invalid-name
4040

@@ -75,7 +75,7 @@ def register_secret(self, secret: typing.Secret):
7575

7676
def register_secret_batch(self, secrets: List[typing.Secret]):
7777
secret_batch = list()
78-
secret_registry_transaction = RaidenAsyncResult()
78+
secret_registry_transaction = AsyncResult()
7979

8080
for secret in secrets:
8181
secrethash = sha3(secret)

raiden/network/proxies/token_network.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections import defaultdict
2+
from gevent.event import AsyncResult
23
from typing import List, NamedTuple, Optional
34

45
import structlog
@@ -46,7 +47,6 @@
4647
privatekey_to_address,
4748
typing,
4849
)
49-
from raiden.utils.gevent_utils import RaidenAsyncResult
5050
from raiden.transfer.utils import hash_balance_data
5151

5252
log = structlog.get_logger(__name__) # pylint: disable=invalid-name
@@ -162,7 +162,7 @@ def new_netting_channel(
162162
# Prevent concurrent attempts to open a channel with the same token and
163163
# partner address.
164164
if partner not in self.open_channel_transactions:
165-
new_open_channel_transaction = RaidenAsyncResult()
165+
new_open_channel_transaction = AsyncResult()
166166
self.open_channel_transactions[partner] = new_open_channel_transaction
167167

168168
try:

raiden/network/transport/matrix.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@
7676
Type,
7777
Iterable,
7878
)
79-
from raiden.utils.gevent_utils import RaidenAsyncResult
8079
from raiden_libs.network.matrix import GMatrixClient, Room
8180

8281

@@ -200,7 +199,6 @@ def start(
200199

201200
# TODO: Add (better) error handling strategy
202201
self._client.start_listener_thread()
203-
self._client.sync_thread.link_exception_safe(self._client_exception_handler)
204202

205203
# TODO: Add greenlet that regularly refreshes our presence state
206204
self._client.set_presence_state(UserPresence.ONLINE.value)
@@ -253,7 +251,7 @@ def send_async(
253251
)
254252

255253
message_id = message.message_identifier
256-
async_result = RaidenAsyncResult()
254+
async_result = AsyncResult()
257255
if isinstance(message, Processed):
258256
async_result.set(True) # processed messages shouldn't get a Delivered reply
259257
self._send_immediate(receiver_address, json.dumps(message.to_dict()))
@@ -845,18 +843,6 @@ def _get_rtt(server_name):
845843
)
846844
return best_server
847845

848-
def _client_exception_handler(self, greenlet):
849-
self._running = False
850-
try:
851-
greenlet.get()
852-
except MatrixError as ex:
853-
gevent.get_hub().handle_system_error(
854-
TransportError,
855-
TransportError(
856-
f'Unexpected error while communicating with Matrix homeserver: {ex}',
857-
),
858-
)
859-
860846
def _sign(self, data: bytes) -> bytes:
861847
""" Use eth_sign compatible hasher to sign matrix data """
862848
return signing.sign(

raiden/network/transport/udp/udp_transport.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
retry_with_recovery,
3939
)
4040
from raiden.raiden_service import RaidenService
41-
from raiden.utils.gevent_utils import RaidenAsyncResult, RaidenGreenletEvent
4241

4342
log = structlog.get_logger(__name__) # pylint: disable=invalid-name
4443

@@ -159,7 +158,7 @@ def __init__(self, discovery, udpsocket, throttle_policy, config):
159158
self.nat_keepalive_timeout = config['nat_keepalive_timeout']
160159
self.nat_invitation_timeout = config['nat_invitation_timeout']
161160

162-
self.event_stop = RaidenGreenletEvent()
161+
self.event_stop = Event()
163162

164163
self.greenlets = list()
165164
self.addresses_events = dict()
@@ -253,8 +252,8 @@ def start_health_check(self, recipient):
253252
)
254253

255254
events = healthcheck.HealthEvents(
256-
event_healthy=RaidenGreenletEvent(),
257-
event_unhealthy=RaidenGreenletEvent(),
255+
event_healthy=Event(),
256+
event_unhealthy=Event(),
258257
)
259258

260259
self.addresses_events[recipient] = events
@@ -365,7 +364,7 @@ def send_async(
365364

366365
# ignore duplicates
367366
if message_id not in self.messageids_to_asyncresults:
368-
self.messageids_to_asyncresults[message_id] = RaidenAsyncResult()
367+
self.messageids_to_asyncresults[message_id] = AsyncResult()
369368

370369
queue = self.get_queue_for(queue_identifier)
371370
queue.put((messagedata, message_id))
@@ -403,7 +402,7 @@ def maybe_sendraw_with_result(
403402
"""
404403
async_result = self.messageids_to_asyncresults.get(message_id)
405404
if async_result is None:
406-
async_result = RaidenAsyncResult()
405+
async_result = AsyncResult()
407406
self.messageids_to_asyncresults[message_id] = async_result
408407

409408
host_port = self.get_host_port(recipient)

raiden/network/transport/udp/udp_utils.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
_AbstractLinkable,
66
Event,
77
)
8-
from raiden.utils.gevent_utils import RaidenGreenletEvent
98
from raiden.utils import typing
109
# type alias to avoid both circular dependencies and flake8 errors
1110
UDPTransport = 'UDPTransport'
@@ -17,13 +16,13 @@ def event_first_of(*events: _AbstractLinkable) -> Event:
1716
The event returned is /not/ cleared with any of the `events`, this value
1817
must not be reused if the clearing behavior is used.
1918
"""
20-
first_finished = RaidenGreenletEvent()
19+
first_finished = Event()
2120

2221
if not all(isinstance(e, _AbstractLinkable) for e in events):
2322
raise ValueError('all events must be linkable')
2423

2524
for event in events:
26-
event.rawlink_safe(lambda _: first_finished.set())
25+
event.rawlink(lambda _: first_finished.set())
2726

2827
return first_finished
2928

raiden/raiden_service.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from coincurve import PrivateKey
1010
from eth_utils import is_binary_address
11+
from gevent.event import AsyncResult, Event
1112
from gevent.lock import Semaphore
1213

1314
from raiden import constants, routing, waiting
@@ -51,7 +52,6 @@
5152
create_default_identifier,
5253
typing,
5354
)
54-
from raiden.utils.gevent_utils import RaidenAsyncResult, RaidenGreenletEvent
5555

5656
log = structlog.get_logger(__name__) # pylint: disable=invalid-name
5757

@@ -145,7 +145,7 @@ def __init__(
145145
self.tokennetworkids_to_connectionmanagers = dict()
146146
self.identifier_to_results: typing.Dict[
147147
typing.PaymentIdentifier,
148-
RaidenAsyncResult,
148+
AsyncResult,
149149
] = dict()
150150

151151
self.chain: BlockChainService = chain
@@ -164,8 +164,8 @@ def __init__(
164164
self.blockchain_events = BlockchainEvents()
165165
self.alarm = AlarmTask(chain)
166166
self.shutdown_timeout = config['shutdown_timeout']
167-
self.stop_event = RaidenGreenletEvent()
168-
self.start_event = RaidenGreenletEvent()
167+
self.stop_event = Event()
168+
self.start_event = Event()
169169
self.chain.client.inject_stop_event(self.stop_event)
170170

171171
self.wal = None
@@ -194,7 +194,7 @@ def __init__(
194194

195195
self.event_poll_lock = gevent.lock.Semaphore()
196196

197-
def start_async(self) -> RaidenGreenletEvent:
197+
def start_async(self) -> Event:
198198
""" Start the node asynchronously. """
199199
self.start_event.clear()
200200
self.stop_event.clear()
@@ -299,13 +299,13 @@ def start_async(self) -> RaidenGreenletEvent:
299299
def set_start_on_registration(_):
300300
self.start_event.set()
301301

302-
endpoint_registration_greenlet.link_safe(set_start_on_registration)
302+
endpoint_registration_greenlet.link(set_start_on_registration)
303303
else:
304304
self.start_event.set()
305305

306306
return self.start_event
307307

308-
def start(self) -> RaidenGreenletEvent:
308+
def start(self) -> Event:
309309
""" Start the node. """
310310
self.start_async().wait()
311311

@@ -592,7 +592,7 @@ def start_mediated_transfer(
592592
if identifier in self.identifier_to_results:
593593
return self.identifier_to_results[identifier]
594594

595-
async_result = RaidenAsyncResult()
595+
async_result = AsyncResult()
596596
self.identifier_to_results[identifier] = async_result
597597

598598
secret = random_secret()

0 commit comments

Comments
 (0)