Concurrent requests requiring updates results in blocked threads
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
certify-web |
Fix Released
|
Critical
|
Marc Tardif |
Bug Description
When multiple concurrent requests are made on a page which requires updates of the same row in the database, some deadlock is causing Zope3 threads to block on the shared resource. To reproduce the problem, first create a URL which updates the database. For example, the following line could be added to the title attribute of the hardware page:
self.
Second, call ab (apache benchmark) on the URL of some hardware object:
# ab \
-c 20 \
-n 40 \
-C certify_
http://
The output of the appserver will show many update errors, which is quite normal because the databases are configured with serializable isolation:
ProgrammingError: could not serialize access due to concurrent update
However, what is not normal is that the appserver is then blocked, ie ctrl-c doesn't even kill the process. It seems that only sending a SIGKILL manages to free the process. Furthermore, the following postgres processes can be seen in the ps output:
postgres 14091 0.0 0.6 42280 5068 ? Ss 14:37 0:00 postgres: cr3 certify_trunk_main [local] UPDATE waiting
postgres 14103 0.0 0.6 42292 4972 ? Ss 14:37 0:00 postgres: cr3 certify_trunk_main [local] UPDATE waiting
postgres 14106 0.0 0.6 42296 4972 ? Ss 14:37 0:00 postgres: cr3 certify_trunk_main [local] UPDATE waiting
postgres 14118 0.0 0.6 42296 4964 ? Ss 14:37 0:00 postgres: cr3 certify_trunk_main [local] UPDATE waiting
So, the appserver seems to be blocked on these database requests.
Marc Tardif (cr3) wrote : | #1 |
Changed in certify-web: | |
assignee: | nobody → cr3 |
importance: | Undecided → Critical |
status: | New → Confirmed |
Marc Tardif (cr3) wrote : | #2 |
After attaching to the Python process with gdb, the following stack trace seems to indicate that the appserver is locked on acquiring a thread. This is a call done in Python, so at least the problem doesn't seem to be in a compiled module.
#0 0xb7f60410 in __kernel_vsyscall ()
#1 0xb7f44d05 in sem_wait@@GLIBC_2.1 () from /lib/tls/
#2 0x080f2445 in PyThread_
#3 0x080c33fc in PyEval_
#4 0x08070194 in ?? ()
#5 0x080c9ab3 in PyEval_EvalFrameEx ()
#6 0x080c96e5 in PyEval_EvalFrameEx ()
#7 0x080c96e5 in PyEval_EvalFrameEx ()
#8 0x080c96e5 in PyEval_EvalFrameEx ()
#9 0x080c96e5 in PyEval_EvalFrameEx ()
#10 0x080cb1f7 in PyEval_EvalCodeEx ()
#11 0x081136b6 in ?? ()
#12 0x0805cb97 in PyObject_Call ()
#13 0x08062bfb in ?? ()
#14 0x0805cb97 in PyObject_Call ()
#15 0x080c2e9c in PyEval_
#16 0x080c27c4 in ?? ()
#17 0x080c9ab3 in PyEval_EvalFrameEx ()
#18 0x080cb1f7 in PyEval_EvalCodeEx ()
#19 0x080c93fe in PyEval_EvalFrameEx ()
#20 0x080c96e5 in PyEval_EvalFrameEx ()
#21 0x080c96e5 in PyEval_EvalFrameEx ()
#22 0x080cb1f7 in PyEval_EvalCodeEx ()
#23 0x080c93fe in PyEval_EvalFrameEx ()
#24 0x080cb1f7 in PyEval_EvalCodeEx ()
#25 0x080c93fe in PyEval_EvalFrameEx ()
#26 0x080c96e5 in PyEval_EvalFrameEx ()
#27 0x080c96e5 in PyEval_EvalFrameEx ()
#28 0x080c96e5 in PyEval_EvalFrameEx ()
#29 0x080c96e5 in PyEval_EvalFrameEx ()
#30 0x080c96e5 in PyEval_EvalFrameEx ()
#31 0x080c96e5 in PyEval_EvalFrameEx ()
#32 0x080cb1f7 in PyEval_EvalCodeEx ()
#33 0x081136b6 in ?? ()
#34 0x0805cb97 in PyObject_Call ()
#35 0x080c7e04 in PyEval_EvalFrameEx ()
#36 0x080c96e5 in PyEval_EvalFrameEx ()
#37 0x080c96e5 in PyEval_EvalFrameEx ()
#38 0x080c96e5 in PyEval_EvalFrameEx ()
#39 0x080cb1f7 in PyEval_EvalCodeEx ()
#40 0x081136b6 in ?? ()
#41 0x0805cb97 in PyObject_Call ()
#42 0x080c7e04 in PyEval_EvalFrameEx ()
#43 0x080c96e5 in PyEval_EvalFrameEx ()
#44 0x080c96e5 in PyEval_EvalFrameEx ()
#45 0x080cb1f7 in PyEval_EvalCodeEx ()
#46 0x081136b6 in ?? ()
#47 0x0805cb97 in PyObject_Call ()
#48 0x080c7e04 in PyEval_EvalFrameEx ()
#49 0x080c96e5 in PyEval_EvalFrameEx ()
#50 0x080c96e5 in PyEval_EvalFrameEx ()
#51 0x080c96e5 in PyEval_EvalFrameEx ()
#52 0x080cb1f7 in PyEval_EvalCodeEx ()
#53 0x081136b6 in ?? ()
#54 0x0805cb97 in PyObject_Call ()
#55 0x080c7e04 in PyEval_EvalFrameEx ()
#56 0x080c96e5 in PyEval_EvalFrameEx ()
#57 0x080c96e5 in PyEval_EvalFrameEx ()
#58 0x080cb1f7 in PyEval_EvalCodeEx ()
#59 0x080c93fe in PyEval_EvalFrameEx ()
#60 0x080cb1f7 in PyEval_EvalCodeEx ()
#61 0x080c93fe in PyEval_EvalFrameEx ()
#62 0x080cb1f7 in PyEval_EvalCodeEx ()
#63 0x080c93fe in PyEval_EvalFrameEx ()
#64 0x080cb1f7 in PyEval_EvalCodeEx ()
#65 0x080cb347 in PyEval_EvalCode ()
#66 0x080ea818 in PyRun_FileExFlags ()
#67 0x080eaab9 in PyRun_SimpleFil
#68 0x08059335 in Py_Main ()
#69 0x080587f2 in main ()
Marc Tardif (cr3) wrote : | #3 |
In an attempt to potentially isolate the problem further, the following multi-threaded application was written which strictly makes Storm calls. The database queries are those which were observed in the PostgreSQL log when running the above request to reproduce the problem. Unfortunately, this doesn't seem to reproduce the blocking problem which seems to indicate that the problem might be in the Zope3 threading pool.
import sys, os
import transaction
from threading import Thread
from zope.component import getUtility
from canonical.
from canonical.
THREAD_COUNT = 40
ITERATION_COUNT = 40
def do_function(
main_store = getUtility(
for i in range(ITERATION
try:
result = main_store.
result = main_store.
result = main_store.
result = main_store.
result = main_store.
result = main_store.
result = main_store.
result = main_store.
result = main_store.
except Exception, e:
print e
def main(argv):
load_
threads = []
for i in range(THREAD_
thread = Thread(
while True:
alives = [t for t in threads if t.isAlive()]
for alive in alives:
try:
except KeyboardInterrupt:
else:
break
return 0
if __name__ == "__main__":
sys.
Marc Tardif (cr3) wrote : | #4 |
After discussing the problem with the Landscape team which happens to share some code, Thomas seemed to indicate that they have encountered a similar problem detailed in bug #294118. They have initially observed the problem when users would click on the submit button twice in a row. This basically reproduces the same observations detailed in this bug where the same row in the database gets updated concurrently. The bug is still open so there is no known solution yet.
The reason why the certification website has encountered the problem more regularly than the Landscape website is that our client is more aggressive when submitting information to the website compared to their client. So, a workaround would be to relax the client. However, this would only be a workaround to a problem which would remain lurking for many other potential situations. Ideally, locks should be handled reliably and deadlocks should not be possible even under heavy load.
Marc Tardif (cr3) wrote : | #5 |
After running the appserver with the trace module and the -t argument, python -m trace -t, and disabling the mail queue delivery to prevent repetitive calls from being made, the following few lines are output last. I'm still not sure if this provides further insight into the problem but worth noting for now.
...
postgres.py(199): return compile_
--- modulename: expr, funcname: compile_sql_token
expr.py(1284): if is_safe_token(expr) and not compile.
expr.py(1285): return expr
info.py(247): if state.context is TABLE and issubclass(expr, ClassAlias):
info.py(249): return table
--- modulename: expr, funcname: <genexpr>
expr.py(538): tables = set(compile(table, state, token=True) for table in tables)
--- modulename: info, funcname: compile_type
info.py(245): cls_info = get_cls_info(expr)
--- modulename: info, funcname: get_cls_info
info.py(47): if "__storm_
info.py(49): return cls.__dict_
info.py(246): table = compile(
--- modulename: postgres, funcname: compile_
postgres.py(196): if "." in expr and state.context in (TABLE, COLUMN_PREFIX):
postgres.py(199): return compile_
--- modulename: expr, funcname: compile_sql_token
expr.py(1284): if is_safe_token(expr) and not compile.
expr.py(1285): return expr
info.py(247): if state.context is TABLE and issubclass(expr, ClassAlias):
info.py(249): return table
--- modulename: expr, funcname: <genexpr>
expr.py(538): tables = set(compile(table, state, token=True) for table in tables)
expr.py(539): return ", ".join(
expr.py(641): parameters = state.parameters
expr.py(642): state.pop()
--- modulename: expr, funcname: pop
expr.py(262): setattr(self, *self._
expr.py(643): state.parameter
expr.py(644): state.pop()
--- modulename: expr, funcname: pop
expr.py(262): setattr(self, *self._
expr.py(645): state.pop()
--- modulename: expr, funcname: pop
expr.py(262): setattr(self, *self._
expr.py(646): return "".join(tokens)
database.py(201): params = state.parameters
database.py(202): statement = convert_
--- modulename: database, funcname: convert_param_marks
database.py(380): if from_param_mark == to_param_mark or from_param_mark not in statement:
database.py(382): tokens = statement.
database.py(383): for i in range(0, len(tokens), 2):
database.py(384): tokens[i] = tokens[
database.py(383): for i in range(0, len(tokens), 2):
database.py(385): return "'".join(tokens)
database.py(203): raw_cursor = self.raw_
--- modulename: postgres, funcname: raw_execute
postgres.py(290): if type(statement) is unicode:
postgres.py(292): statement = statement.encod...
Marc Tardif (cr3) wrote : | #6 |
To follow up on the above trace, the last line was:
database.py(325): return function(*args, **kwargs)
So, by placing a print statement above that line to display the args, the following line is output:
('UPDATE hardware SET secure_id=E%s, confidential=%s, releases=ARRAY[%s], category=E%s, certification=E%s, soft_lock_time=%s, aliases=%s, canonical_id=E%s, foo_time=%s, make=%s, related_
This is confirmed by the PostgreSQL database which also shows a few instances of that very same query in the pg_stat_activity table:
certify_
UPDATE hardware SET secure_
There are actually 9 instances of that query in the table which happens to correspond to the same number of processes output by the ps command looking like: UPDATE waiting.
Furthermore, there are also 9 rows matching the process ids for the above queries where locks have not yet been granted in the pg_locks table. The trace, the processes and the database are all in agreement.
Marc Tardif (cr3) wrote : | #7 |
In the pg_locks table, the 9 rows matching the blocked process ids only has one row in ShareLock mode whereas all the others are in ExclusiveLock mode. To focus on the former process, the following lines are output in the PostgreSQL log for the corresponding transaction. The interesting thing to notice is that the blocking query seems to be the first one in the list:
UPDATE hardware SET secure_
SELECT 1 FROM record_type WHERE record_
SELECT record_
SELECT 1 FROM hardware WHERE hardware.id = 123
SELECT hardware.
UPDATE hardware SET foo_time=
UPDATE hardware SET foo_time=
SELECT 1 FROM account WHERE account.id = 15
SELECT account.
SELECT 1 FROM hardware WHERE hardware.id = 313
SELECT DISTINCT hardware.
UPDATE hardware SET secure_
SELECT DISTINCT hardware.
Marc Tardif (cr3) wrote : | #8 |
For completeness purposes, anyone trying to reproduce this bug would be recommended to make the following changes to their postgresql.conf file:
log_error_verbosity = verbose
log_line_prefix = '%t %x '
log_lock_waits = on
Marc Tardif (cr3) wrote : | #9 |
There is apparently a Zope3 component called DeadlockDebugger which claims to add a hook so that a deadlocked process can be debugged. The component is available from the Zope svn repository:
svn co svn://svn.
After having enabled the module and starting the appserver, it indeed seems to live up to its claim. However, once the appserver is locked, the page no longer responds. This makes sense because the pool of threads is probably locked which prevents another thread from being acquired from the pool for debugging purposes.
Marc Tardif (cr3) wrote : | #10 |
Another approach attempted was to log stack frames to file from a separate thread. For example, the following code repeatedly writes the frames every 10 seconds. However, when Zope3 blocks, this thread also seems to stop.
def runforever(name):
import threadframe, traceback, time, sys
file = open(name, "w")
while True:
for id, frame in threadframe.
import thread
thread.
Marc Tardif (cr3) wrote : | #11 |
For completeness purposes, the following strace output confirms once again that the threads are blocked on a call to lock.acquire():
# strace -fp `cat certify_web.pid`
Process 18255 attached with 14 threads - interrupt to quit
[pid 18223] futex(0x81c2740, 0x80 /* FUTEX_??? */, 0 <unfinished ...>
[pid 18224] futex(0x81c2740, 0x80 /* FUTEX_??? */, 0 <unfinished ...>
[pid 18225] futex(0x81c2740, 0x80 /* FUTEX_??? */, 0 <unfinished ...>
[pid 18226] futex(0x81c2740, 0x80 /* FUTEX_??? */, 0 <unfinished ...>
[pid 18246] restart_
[pid 18247] restart_
[pid 18248] restart_
[pid 18249] restart_
[pid 18250] restart_
[pid 18251] futex(0x81c2740, 0x80 /* FUTEX_??? */, 0 <unfinished ...>
[pid 18252] restart_
[pid 18253] futex(0xb76fdf8, 0x80 /* FUTEX_??? */, 2 <unfinished ...>
[pid 18254] restart_
[pid 18255] restart_
Marc Tardif (cr3) wrote : | #12 |
To follow up on the gdb session, I attempted to get a stack trace from there by calling the C function PyRun_SimpleString. At first, this resulted in a STOP signal being sent. Then, by setting the unwindonsignal option, it was finally possible to obtain a stack trace:
(gdb) set unwindonsignal on
(gdb) call PyRun_SimpleStr
$1 = 0
In the output, there is unfortunately not much more information than has already been gathered. It basically shows that Python is blocked on the UPDATE query made to the PostgreSQL database:
...
File "/srv/trunk.
self.
File "/srv/trunk.
return function(*args, **kwargs)
File "<string>", line 1, in <module>
Marc Tardif (cr3) wrote : | #13 |
After looking more closely at the threads using gdb, it seems that most of them are not really blocked while waiting to acquire a lock but they are actually blocked on a call to psycopg2. One potential solution was to try upgrading to the latest version of the library but, after building and installing, the same problem occured. Unfortunately, this doesn't leave much to go on considering that the Storm code only really uses psycopg2 to obtain a raw connection.
(gdb) thread 2
(gdb) where
#0 0xb7f1c410 in __kernel_vsyscall ()
#1 0xb7e43c07 in poll () from /lib/tls/
#2 0xb5d5910a in ?? () from /usr/lib/libpq.so.5
#3 0xb5d59246 in ?? () from /usr/lib/libpq.so.5
#4 0xb5d592c3 in ?? () from /usr/lib/libpq.so.5
#5 0xb5d58972 in PQgetResult () from /usr/lib/libpq.so.5
#6 0xb5d58a8c in ?? () from /usr/lib/libpq.so.5
#7 0xb5d7f9b2 in ?? () from /usr/lib/
#8 0xb5d85cdc in ?? () from /usr/lib/
#9 0xb5d86463 in ?? () from /usr/lib/
#10 0x0805cb97 in PyObject_Call (func=0x1, arg=0xba3c8ac, kw=0xba2968c) at ../Objects/
#11 0x080c7e04 in PyEval_EvalFrameEx (f=0xa0d500c, throwflag=0) at ../Python/
#12 0x080cb1f7 in PyEval_EvalCodeEx (co=0x8df2020, globals=0x8df03e4, locals=0x0, args=0xbc2e2b8, argcount=4, kws=0x0, kwcount=0, defs=0x0, defcount=0, closure=0x0)
at ../Python/
...
Marc Tardif (cr3) wrote : | #14 |
Another potential solution was to use the PostgresTimeout
postgres 21427 0.0 0.6 42280 5064 ? Ss 19:59 0:00 postgres: cr3 certify_trunk_main [local] UPDATE waiting
After waiting the specified timeout, only one process was remaining. However, that particular process was causing the appserver to cease responding. Note that this solution is just a workaround because a request blocked on UPDATE is not necessarily the same as a runaway request. The difference is that the former is just waiting whereas the other is assumed to be performing a request much longer than expected.
James Henstridge (jamesh) wrote : | #15 |
To debug this, you could try issuing the following query when things are deadlocked:
select datname, usename, xact_start, waiting, current_query from pg_stat_activity;
This should tell you what is going on from PostgreSQL's point of view. The deadlocked connections should show up with "waiting" set to true. If you have any "<IDLE> in transaction" connections, they are possible causes of the deadlock.
Marc Tardif (cr3) wrote : | #16 |
After I reproduce the problem once again and run that query on the pg_stat_activity table, I see the same three updates which are the only queries in the waiting state:
UPDATE hardware SET record_
This actually corresponds to the same three processes described as waiting returned by the ps command:
postgres 6568 0.0 0.6 42280 5008 ? Ss 23:38 0:00 postgres: cr3 certify_trunk_main [local] UPDATE waiting
Strangely though, when I attach to the appserver process using gdb and iterate over each thread, I seem to count twice as many threads blocked on psycopg2:
#0 0xb7f96410 in __kernel_vsyscall ()
#1 0xb7f7a589 in __lll_lock_wait () from /lib/tls/
#2 0xb7f75ba6 in _L_lock_95 () from /lib/tls/
#3 0xb7f7558a in pthread_mutex_lock () from /lib/tls/
#4 0xb7852871 in ?? () from /usr/lib/
#5 0xb7853154 in ?? () from /usr/lib/
#6 0x0805cb97 in PyObject_Call (func=0x80, arg=0xbad6f8c, kw=0x89999bc) at ../Objects/
#7 0x080c7e04 in PyEval_EvalFrameEx (f=0xbbb36e4, throwflag=0) at ../Python/
...
Marc Tardif (cr3) wrote : | #17 |
Upon closer inspection, it seems likely that the problem might be in the Connection.
def _check_
try:
return function(*args, **kwargs)
except DatabaseError, exc:
if self.is_
else:
It seems that exception might be percolating up to the point where one of the Zope3 threads doesn't commit a transaction. This might be a likely reason why an UPDATE process might endup waiting on an <IDLE> in transaction process. This is a deadlock until the latter process commits its transaction.
Now, whether the ProgrammingError exception should be handled in this method or higher up the stack remains to be determined.
Marc Tardif (cr3) wrote : | #18 |
Here is a database session which might prove interesting for future debugging purposes. This first determines the query which seems to be waiting and then finds the corresponding query on which it's waiting:
# select transactionid from pg_locks where pid in (select procpid from pg_stat_activity where current_query like 'UPDATE%') and mode = 'ShareLock' and granted is false;
transactionid | pid
-------
55215 | 14866
(1 row)
This might be worth explaining a bit. First, the subselect looks for UPDATE queries which seemed to be waiting as determined by the output of the ps command. Second, the ShareLock is the mode in which transactions wait on each other, hence the word "share". Last, the query must not have been granted which what we're looking for in the first place.
Given the above transactionid, this should be queried again in the pg_locks table in order to determine which two processes are actually sharing the same transaction:
# select pid from pg_locks where transactionid = 55215;
pid
-------
14880
14866
(2 rows)
So, the other transaction could now be queried in the pg_stat_activity table in order to determine what it's current query might be:
# select current_query from pg_stat_activity where procpid = 14880;
current_query
-------
<IDLE> in transaction
(1 row)
The UPDATE request therefore seems to be waiting on an idle transaction, which is an application side problem where the transaction is not being cleaned up properly.
Marc Tardif (cr3) wrote : | #19 |
For completeness purposes, here is the stack trace of the concurrent update exception as shown in the Zope 3 logs:
2009-01-30T17:29:31 ERROR SiteError http://
Traceback (most recent call last):
File "/srv/trunk.
result = publication.
File "/srv/trunk.
return mapply(ob, request.
File "/srv/trunk.
return debug_call(obj, args)
File "/srv/trunk.
return obj(*args)
File "/srv/trunk.
result = provider.render()
File "/srv/trunk.
return self.template(
File "/srv/trunk.
return self.im_
File "/srv/trunk.
sourceAnnot
File "/srv/trunk.
strictinsert=0, sourceAnnotatio
File "/srv/trunk.
self.
File "/srv/trunk.
handlers[
File "/srv/trunk.
self.
File "/srv/trunk.
return self.no_tag(start, program)
File "/srv/trunk.
self.
File "/srv/trunk.
handlers[
File "/srv/trunk.
text = self.engine.
File "/srv/trunk.
text = self.evaluate(expr)
File "/srv/trunk.
return expression(self)
File "/srv/trunk.
Marc Tardif (cr3) wrote : | #20 |
After looking more closely at the Zope 3 transaction modules and Storm integration, it was interesting to note that a unique data store object is cached on a per thread basis. So, for a given thread, the three phase commit in the Zope 3 transactions iterates over each data store instantiated within that thread and commits them. Since this process is quite robust by design, the problem was assumed to be elsewhere.
Then, after looking at the symptoms again, it seemed that the problem could be caused by a data store being instantiated outside of the Zope 3 transaction process. Or, by a data store being removed from the Zope 3 transaction process but cached elsewhere. In other words, if Zope 3 doesn't know about a particular data store, it won't perform the three phase commit on it. Therefore, a transaction might not be committed which would result in an idle transaction.
That being said, closer attention was given to the concept of Zope 3 utilities which are persisted across all threads. Then, the following code was found in the canonical.
class LazyStore(object):
def __get__(self, obj, cls=None):
if obj is not None:
return obj.store
return self
class StoreHolder(
store = LazyStore()
So, if a utility had the misfortune of inheriting from the StoreHolder base class, then it would have the same data store cached across all threads which would result in the behavior detailed above. The solution is therefore to make sure that the utilities don't inherit from this class.
Changed in certify-web: | |
status: | Confirmed → In Progress |
Stuart Bishop (stub) wrote : | #21 |
There must be other transactions in process if you have three queries in waiting - something else is holding the lock they are waiting on. What is the output of 'select datname, usename, xact_start, waiting, current_query from pg_stat_activity;' ? The blocked transactions are not the interesting ones, the other ones holding the locks open are.
This isn't actually a database deadlock, as PostgreSQL detects deadlocks and aborts one of the transactions (see the deadlock_timeout setting in postgresql.conf). It will be another active connection, or an <IDLE in transaction> connection. If it is the latter, you need to track down why that connection has not been committed or rolled back.
Marc Tardif (cr3) wrote : | #22 |
The problem was that the idle transaction was not being committed because the three phase commit in Zope 3 was not aware of a data store, so it could not commit that particular store. The reason was that a Zope 3 utility in the certification code was cashing the data store in the object whereas that object was being used across threads. This has been fixed by no longer caching the data store in the StoreHolder and letting the StoreSelector delegate caching to the ZStorm module.
Changed in certify-web: | |
status: | In Progress → Fix Released |
In an attempt to obtain a stack trace of the threads when they are in a blocked state, the following code was added which generates a trace for all running threads when SIGUSR2 is sent to the process. The only problem is that when the appserver is blocked, the signal is never caught. However, it works fine when the appserver is started so Zope3 doesn't seem to be overwriting the signal.
import signal handler( ):
import thread
import threading
import traceback
def setup_trace_
"""Configure the Python interpreter to dump stack traces for all running threads on a SIGUSR2"""
# if threading. Thread. getIdent does not exist, monkeypatch threading.Thread threading. Thread, 'getIdent'):
old_bootstrap = threading. Thread. _Thread_ _bootstrap Thread_ _bootstrap( self):
self. _Thread_ _ident = thread.get_ident() Thread_ getIdent( self):
return None
threading. Thread. _Thread_ _bootstrap = _monkeypatch_ Thread_ _bootstrap
threading. Thread. getIdent = _monkeypatch_ Thread_ getIdent
if not hasattr(
def _monkeypatch_
return old_bootstrap(self)
def _monkeypatch_
if not hasattr(self, '_Thread__ident'):
return self._Thread__ident
def _dump_traces(_, __):
thread_ id_map = {} enumerate( ):
thread_ id_map[ thread_ obj.getIdent( )] = thread_ obj.getName( ) frames( ).items( ):
print >>sys.stderr, '%s' % (thread_ id_map. get(thread_ id, 'threadid-%s' % (thread_id,)))
traceback. print_stack( frame, file=sys.stderr) signal( signal. SIGUSR2, _dump_traces)
try:
print >>sys.stderr, 'Threads:'
for thread_obj in threading.
for (thread_id, frame) in sys._current_
except Exception, e:
print >>sys.stderr, e
signal.