From b284b887461ea1790d0e3cfe62a27bb869551211 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 30 Aug 2023 15:31:13 +0000 Subject: [PATCH] add stacktracer.py: helper util to debug threading issues taken from https://code.activestate.com/recipes/577334-how-to-debug-deadlocked-multi-threaded-programs/ --- electrum/utils/__init__.py | 0 electrum/utils/stacktracer.py | 132 ++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 electrum/utils/__init__.py create mode 100644 electrum/utils/stacktracer.py diff --git a/electrum/utils/__init__.py b/electrum/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/electrum/utils/stacktracer.py b/electrum/utils/stacktracer.py new file mode 100644 index 000000000..a1fe4ea18 --- /dev/null +++ b/electrum/utils/stacktracer.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python +# +# Copyright (C) 2010 Laszlo Nagy (nagylzs) +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Taken from: https://code.activestate.com/recipes/577334-how-to-debug-deadlocked-multi-threaded-programs/ + + +"""Stack tracer for multi-threaded applications. + + +Usage: + +import stacktracer +stacktracer.start_trace("trace.html",interval=5,auto=True) # Set auto flag to always update file! +.... +stacktracer.stop_trace() +""" + +import sys +import traceback +from pygments import highlight +from pygments.lexers import PythonLexer +from pygments.formatters import HtmlFormatter + + +# Taken from http://bzimmer.ziclix.com/2008/12/17/python-thread-dumps/ + +def stacktraces(): + code = [] + for threadId, stack in sys._current_frames().items(): + code.append("\n# ThreadID: %s" % threadId) + for filename, lineno, name, line in traceback.extract_stack(stack): + code.append('File: "%s", line %d, in %s' % (filename, lineno, name)) + if line: + code.append(" %s" % (line.strip())) + + return highlight("\n".join(code), PythonLexer(), HtmlFormatter( + full=False, + # style="native", + noclasses=True, + )) + + +# This part was made by nagylzs +import os +import time +import threading + + +class TraceDumper(threading.Thread): + """Dump stack traces into a given file periodically.""" + + def __init__(self, fpath, interval, auto): + """ + @param fpath: File path to output HTML (stack trace file) + @param auto: Set flag (True) to update trace continuously. + Clear flag (False) to update only if file not exists. + (Then delete the file to force update.) + @param interval: In seconds: how often to update the trace file. + """ + assert (interval > 0.1) + self.auto = auto + self.interval = interval + self.fpath = os.path.abspath(fpath) + self.stop_requested = threading.Event() + threading.Thread.__init__(self) + + def run(self): + while not self.stop_requested.isSet(): + time.sleep(self.interval) + if self.auto or not os.path.isfile(self.fpath): + self.stacktraces() + + def stop(self): + self.stop_requested.set() + self.join() + try: + if os.path.isfile(self.fpath): + os.unlink(self.fpath) + except: + pass + + def stacktraces(self): + fout = file(self.fpath, "wb+") + try: + fout.write(stacktraces()) + finally: + fout.close() + + +_tracer = None + + +def trace_start(fpath, interval=5, auto=True): + """Start tracing into the given file.""" + global _tracer + if _tracer is None: + _tracer = TraceDumper(fpath, interval, auto) + _tracer.setDaemon(True) + _tracer.start() + else: + raise Exception("Already tracing to %s" % _tracer.fpath) + + +def trace_stop(): + """Stop tracing.""" + global _tracer + if _tracer is None: + raise Exception("Not tracing, cannot stop.") + else: + _trace.stop() + _trace = None