Skip to content

Commit

Permalink
Add asyncio stack debugging to USR1 signal
Browse files Browse the repository at this point in the history
This adds `print_stack()` calls for all current (if any) `asyncio` tasks
to the already existing debugging signal hook listening for `SIGUSR1`.

For reference: #7141 (comment)
  • Loading branch information
konradkonrad committed Aug 23, 2021
1 parent 926971d commit c9e2f2c
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 9 deletions.
6 changes: 3 additions & 3 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
from raiden.tests.integration.exception import RetryTestError
from raiden.tests.utils.transport import make_requests_insecure
from raiden.utils.cli import LogLevelConfigType
from raiden.utils.debugging import enable_gevent_monitoring_signal
from raiden.utils.debugging import enable_monitoring_signal
from raiden.utils.ethereum_clients import VersionSupport, is_supported_client

log = structlog.get_logger()
Expand Down Expand Up @@ -171,8 +171,8 @@ def check_parity_version_for_tests(blockchain_type):


@pytest.fixture(scope="session", autouse=True)
def auto_enable_gevent_monitoring_signal():
enable_gevent_monitoring_signal()
def auto_enable_monitoring_signal():
enable_monitoring_signal()


@pytest.fixture(scope="session", autouse=True)
Expand Down
4 changes: 2 additions & 2 deletions raiden/ui/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
option,
option_group,
)
from raiden.utils.debugging import IDLE, enable_gevent_monitoring_signal
from raiden.utils.debugging import IDLE, enable_monitoring_signal
from raiden.utils.formatting import to_checksum_address
from raiden.utils.system import get_system_spec
from raiden.utils.typing import MYPY_ANNOTATION, ChainID
Expand Down Expand Up @@ -596,7 +596,7 @@ def _run(ctx: Context, **kwargs: Any) -> None:
set_by = source.name.title() if source else None
log.debug("Using config file", config_file=kwargs["config_file"], set_by=set_by)

enable_gevent_monitoring_signal()
enable_monitoring_signal()

if ctx.invoked_subcommand is not None:
return
Expand Down
25 changes: 21 additions & 4 deletions raiden/utils/debugging.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import os
import signal
import time
Expand All @@ -17,10 +18,15 @@
log = structlog.get_logger(__name__)


def enable_gevent_monitoring_signal() -> None:
"""Install a signal handler for SIGUSR1 that executes gevent.util.print_run_info().
This can help evaluating the gevent greenlet tree.
See http://www.gevent.org/monitoring.html for more information.
def enable_monitoring_signal() -> None:
"""Install a signal handler for SIGUSR1 that executes ``gevent.util.print_run_info()`` as well
as ``task.print_stack()`` for all asyncio tasks.
This can help evaluating the gevent greenlet tree and the asyncio stack, which can be useful
when debugging potential deadlocks.
See
- http://www.gevent.org/monitoring.html
- https://docs.python.org/3/library/asyncio-task.html#asyncio.Task.print_stack
for more information.
Usage:
pytest [...]
Expand All @@ -30,6 +36,7 @@ def enable_gevent_monitoring_signal() -> None:

def on_signal(signalnum: Any, stack_frame: Any) -> None: # pylint: disable=unused-argument
gevent.util.print_run_info()
debug_asyncio()

if os.name == "nt":
# SIGUSR1 not supported on Windows
Expand All @@ -38,6 +45,16 @@ def on_signal(signalnum: Any, stack_frame: Any) -> None: # pylint: disable=unus
signal.signal(signal.SIGUSR1, on_signal)


def debug_asyncio() -> None:
try:
asyncio.get_running_loop()
except RuntimeError:
return
tasks = [task for task in asyncio.all_tasks() if task is not asyncio.current_task()]
for task in tasks:
task.print_stack()


def limit_thread_cpu_usage_by_time() -> None:
"""This will enable Gevent's monitoring thread, and if a Greenlet uses the
CPU for longer than `max_blocking_time` it will be killed.
Expand Down

0 comments on commit c9e2f2c

Please sign in to comment.