-
Notifications
You must be signed in to change notification settings - Fork 248
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Idea: greenlet switch detection callback #5
Comments
Actually, I think the only needed callback argument is the target greenlet, since current greenlet can be requested with getcurrent(). |
I think having both greenlets doesn't hurt and probably makes things easier. It also allows us to convert it to a "post-switch callback". I currently can't think of a reason why one would prefer one over the other however. what should happen if the callback tries to switch greenlets? other than that it looks good to me. |
This would be amazing. Having both the current and target greenlet makes sense to me -- for the profiling use case, you'd always need both anyway. I also can't think of any reasons why post-switch would be better than pre-switch, but passing both greenlets leaves the option open, as you mentioned. I can't think of any profiling-related things I'd want in this beyond the callback itself and the greenlet info. I'm kind of torn about greenlet switching inside the callback. My first reaction was to prevent it and throw some horrible exception. After thinking about it for a bit I'm less sure about that. I can't think of any reason that greenlet switching in the callback (and thus reentrantly calling the callback again) would be a good thing, but maybe someone else can think of some awesome thing this enables. Maybe just let the user deal with it. If they do something that switches greenlets inside the callback, it's their problem. Probably not worth writing a lot of code to handle gracefully. Anyway, this would be great and will make profiling much easier and more accurate. |
I just realized that for tracing/profiling case there's a possible major issue if callback is implemented trivially: trace would trigger inside the callback. It seems there are some protective fields in thread state though: use_tracing and tracing, which would prevent tracing from running. As for switching inside callbacks, it might indeed cause a lot of problems, either recursive switching, or if callbacks are disabled while inside callbacks it might introduce logical error: from the point of view of switching greenlet the value was already sent, but switching may lead to delayed events, that may lead to unexpected throws back into the greenlet (e.g. timeouts). The easiest solution is a flag that current greenlet is inside callback right now, and switch/throw raising GreenletError if the flag is set. This would enforce that callbacks should be simple (no blocking queues or database queries), but keep strict switching ordering intact, impossible to break even accidentally. |
As for the implementation I think it should be quite easy. New slot in the thread dict for the callback itself, reentrancy check in g_switch, actual callback check/call in g_switchstack, would need to save/restore globals though, since other threads might stomp on them while callback is doing its sleep(777). And as soon as I mapped how this would be actually implemented switching inside callbacks is definitely out, as it would have to be restartable, which is nearly impossible, e.eg not all code paths are restartable: callback might successfully switch into new greenlets, breaking bootstrapping in g_initialstub completely. |
...or maybe not. There are some edge cases (like greenlet deallocations) that would prevent me from putting it into g_switchstack. And since deallocations may lead to switches (which would be disabled), those would need to be deferred just like across threads. Looks like more moving pieces than I thought initially... |
Ok, after a lot of thought, it turned out that disabling switching inside callbacks would be way too complicated (primarily because of switches on greenlet deallocations). This also meant that the only safe place to call callbacks is after doing a switch, when all passaround information is safely processed, but before user code in target greenlet is executed. A work in progress patch is at https://github.com/snaury/greenlet, no docs no tests yet, though:
|
I've only took a short look at the code. One thing I noticed is that the 'reason' argument always get recreated on every switch. I think I would prefer some module level constants (or at least some interned strings). |
There was an even worse bug with switch origin (my logic failed somehow). I just pushed a version that fixes that (by reintroducing ts_origin, for tracing only this time though), plus interns callback strings. Also, "initial" is gone now, I think "switch" and "throw" are more appropriate. It's better if we later introduce proper "start" (after switch/throw trace) and "stop" (before the final switch out) with a single argument, instead of (origin, target) tuple. |
One feature I really need in greenlets, is to be able to enumerate all active greenlets, and dump their stack. Something like: from greenlets import enumerate
import traceback
for g in enumerate():
traceback.print_stack(g.gr_frame) Because right now it's a nightmare to debug deadlocks, or even to understand which piece of code is timing out etc. |
@tarekziade You could do that yourself as follows:
Now, if greenlets are spawned by some library out of your control then you might be a bit out of luck, though. |
@saghul Yes it's the latter : I don't control greenlets creation as they are spawned by various third party code that use gevent. I am trying to debug a weird lock by dumping the stack of each active greenlet when it happens |
You can already do it with gc.get_objects. Just filter greenlet instances. |
@snaury oh! I did not think about this. Trying it now, thx :) |
@snaury I'd prefer to have a slightly different function signature for the tracing function, i.e. def trace(event, origin, target) instead of def trace(event, (origin, target)) The latter is a bit surprising to me. Or is there a good reason not to use the former signature? |
@schmir well, the primary reason is extensibility. The actual signature is:
Similar to Python's:
Only when |
@schmir, also I'm thinking maybe in case of |
ok. We could also use a custom event class with target, origin, exc_info attributes: def trace(event): or use keyword arguments: def trace(type, origin=None, target=None, exc_info=None, **kw): What do you think of those? I like both of them better than passing tuples, but it may be overkill depending on what we end up passing to the trace function. Maybe I should just release 0.4.0 as planned in its current shape.. |
what about using the following signature def trace(event, origin, target, extra) with extra providing any additional information that you like to pass? |
@schmir if you say so, but what about (possible, future) trace events that don't have origin/target? If you're ready to commit to trace calls always having origin/target, then I'll change the code. Custom class will blow up the code significantly (struct layout, attributes, gc traverse/clear, alloc/dealloc, etc.), as for keyword arguments that's too restrictive. Anyway, please tell me what to do and I'll do it. :) @srlindsay what do you think on any of these? |
Alexey Borzenkov writes:
well, we pass in None as origin/target then.
right, that's one of the reasons I proposed the 'extra' argument. It's
why?
I hope I'm able to introduce the 'extra' argument on my own (at least
yes, that would be nice to know. Cheers |
I'd prefer the simplest option that still allows for future extensibility, def trace(event, arg) with the assumption that once we establish what arg is passed for a I don't see that using classes or keyword args allows anything that we The remaining option, def trace(type, origin, target, extra) only makes sense for two use cases:
Anyway, I feel that a generic arg allows us to do everything that I'd want, On Wed, Jun 20, 2012 at 12:49 PM, Ralf Schmitt <
|
Shaun Lindsay writes:
ok, then let's do that and keep the code as it is. |
…opagate to another greenlet and corrupt memory Hello up there. While working on Pygolang I've faced the following problem: a C++ exception thrown in one greenlet, without any try/catch block on that greenlet, might become propagated and "handled" by try/catch block established at another greenlet that happened to switch to the greenlet in question. "Handled" comes in quotes because the program usually segfaults after that. I've also observed segfaults before exception reaches user-level try/catch block - internally in __cxa_throw. Both this issues are likely of the same origin - due to the fact that C-level stack of a greenlet does not start from scratch and starts from C-level stack of the program state when the greenlet is switched to the first time. We already have one test for C++ exception handling in test_cpp that @snaury initially added in d9cb12a. However in that test all greenlets that throw C++ exceptions also put try/catch at the top of their C-stack. In the problem, that I describe, and that added test reproduces, there is no intended top-level try/catch block in C-stack of the greenlet in question that throws. As the test shows the exception becomes propagated to switcher's greenlet context and the program then dies with SIGSEGV: test_exception_switch_and_throw (greenlet.tests.test_cpp.CPPTests) ... terminate called after throwing an instance of 'exception_t' C++ exception unexpectedly caught in g1 <-- NOTE FAIL ====================================================================== FAIL: test_exception_switch_and_throw (greenlet.tests.test_cpp.CPPTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/kirr/src/tools/py/gevent/greenlet/src/greenlet/tests/test_cpp.py", line 62, in test_exception_switch_and_throw (ret, sig, " (core dumped)" if core else "")) AssertionError: failed with ret=0 sig=11 (core dumped) The C-level backtrace from the dumped core is attached in Appendix I. There the program dies somehow after running the code from _test_extension_cpp.cpp module. However with the following dirty-patch the situation becomes more clear: --- a/src/greenlet/tests/_test_extension_cpp.cpp +++ b/src/greenlet/tests/_test_extension_cpp.cpp @@ -70,6 +70,7 @@ test_exception_switch(PyObject* self, PyObject* args) static PyObject* py_test_exception_throw(PyObject* self, PyObject* args) { + abort(); if (!PyArg_ParseTuple(args, "")) return NULL; p_test_exception_throw(0); The C-level backtrace of this abort when, run from under g2, shows that g2 C-stack starts from what was there before for first greenlet with try/catch block: #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50 python-greenlet#1 0x00007f8be31e9537 in __GI_abort () at abort.c:79 python-greenlet#2 0x00007f8be3529423 in py_test_exception_throw (self=0x0, args=0x7f8be317f050) at src/greenlet/tests/_test_extension_cpp.cpp:73 python-greenlet#3 0x00005584d0389903 in PyObject_Call (func=0x7f8be2cdd690, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#4 0x00007f8be3530d63 in g_initialstub (mark=0x7ffe6d1b1c88) at src/greenlet/greenlet.c:931 python-greenlet#5 0x00007f8be3530290 in g_switch (target=0x7f8be2cd6230, args=0x7f8be317f050, kwargs=0x0) at src/greenlet/greenlet.c:692 python-greenlet#6 0x00007f8be35329e2 in PyGreenlet_Switch (g=0x7f8be2cd6230, args=0x7f8be317f050, kwargs=0x0) at src/greenlet/greenlet.c:1806 <- first switch to G2 python-greenlet#7 0x00007f8be35294da in test_exception_switch_and_do_in_g2 (self=0x0, args=0x7f8be2ce33d0) <- this code runs in G1 at src/greenlet/tests/_test_extension_cpp.cpp:105 python-greenlet#8 0x00005584d039de7a in call_function (oparg=<optimized out>, pp_stack=0x7ffe6d1b1e28) at ../Python/ceval.c:4376 python-greenlet#9 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#10 0x00005584d039c3cc in PyEval_EvalCodeEx (co=0x7f8be2cd9cb0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#11 0x00005584d03b6b9b in function_call (func=func@entry=0x7f8be2cde650, arg=0x7f8be317f050, kw=0x0) at ../Objects/funcobject.c:523 python-greenlet#12 0x00005584d0389903 in PyObject_Call (func=0x7f8be2cde650, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#13 0x00007f8be3530d63 in g_initialstub (mark=0x7ffe6d1b20b8) at src/greenlet/greenlet.c:931 python-greenlet#14 0x00007f8be3530290 in g_switch (target=0x7f8be2cd6410, args=0x7f8be317f050, kwargs=0x0) at src/greenlet/greenlet.c:692 python-greenlet#15 0x00007f8be3531c4c in green_switch (self=0x7f8be2cd6410, args=0x7f8be317f050, kwargs=0x0) at src/greenlet/greenlet.c:1321 <- switch to G1 ... The problem might be worked-around with putting try/catch on C-stack for every greenlet, but a more proper fix would be to unlink return address and C-stack-frame of created greenlet from other greenlets completely (i.e. rewrite first frame of C-stack for created greenlet to return to NULL). Thanks beforehand, Kirill P.S. The problem described at python-greenlet#197 (comment) and python-greenlet#205 might be related to hereby issue (/cc @ThePrez, @kadler, @jamadden). P.P.S. The test does not fail on master. I see there @jamadden switched the codebase to C++ almost completely and there is some explicit attempts to save/restore exception state added in python-greenlet@87edf955. However that exception save/restore is specific to Win32 and the test succeeds for me on Linux. I still see the following comment in UserGreenlet::inner_bootstrap // C++ exceptions cannot propagate to the parent greenlet from // here. (TODO: Do we need a catch(...) clause, perhaps on the // function itself? ALl we could do is terminate the program.) ( https://github.com/python-greenlet/greenlet/blob/3e534d6b/src/greenlet/greenlet.cpp#L1085-L1104 ) probably this indeed works only by luck, or due to G_NOEXCEPT modifier of UserGreenlet::inner_bootstrap upon seeing which __cxa_throw stops unwinding C-stack and just calls std::terminate. -------- Appendix I. C-level backtrace after the test died with SIGSEGV #0 0x0000556bdc30d394 in PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3351 python-greenlet#1 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a03acb0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#2 0x0000556bdc325b9b in function_call (func=func@entry=0x7f0b8a03f650, arg=0x7f0b8a4e0050, kw=0x0) at ../Objects/funcobject.c:523 python-greenlet#3 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a03f650, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#4 0x00007f0b8a891d63 in g_initialstub (mark=0x7ffc3ef97988) at src/greenlet/greenlet.c:931 python-greenlet#5 0x00007f0b8a891290 in g_switch (target=0x7f0b8a037410, args=0x7f0b8a4e0050, kwargs=0x0) at src/greenlet/greenlet.c:692 python-greenlet#6 0x00007f0b8a892c4c in green_switch (self=0x7f0b8a037410, args=0x7f0b8a4e0050, kwargs=0x0) at src/greenlet/greenlet.c:1321 python-greenlet#7 0x0000556bdc30ce7a in call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef97ac8) at ../Python/ceval.c:4376 python-greenlet#8 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#9 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a03ad30, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#10 0x0000556bdc312953 in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef97ca8, func=0x7f0b8a03aed0) at ../Python/ceval.c:4471 python-greenlet#11 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef97ca8) at ../Python/ceval.c:4396 python-greenlet#12 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#13 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a03ac30, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#14 0x0000556bdc312953 in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef97e88, func=0x7f0b8a03af50) at ../Python/ceval.c:4471 python-greenlet#15 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef97e88) at ../Python/ceval.c:4396 python-greenlet#16 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#17 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a03adb0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#18 0x0000556bdc312953 in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef98068, func=0x7f0b8a03f250) at ../Python/ceval.c:4471 python-greenlet#19 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef98068) at ../Python/ceval.c:4396 python-greenlet#20 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#21 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a04bd30, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a0752a8, defcount=1, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#22 0x0000556bdc325cc2 in function_call (func=0x7f0b8a0771d0, arg=0x7f0b8a3bd5a0, kw=0x7f0b8a042dd0) at ../Objects/funcobject.c:523 python-greenlet#23 0x0000556bdc30fa96 in PyObject_Call (kw=0x7f0b8a042dd0, arg=0x7f0b8a3bd5a0, func=0x7f0b8a0771d0) at ../Objects/abstract.c:2544 python-greenlet#24 ext_do_call (nk=<optimized out>, na=1, flags=<optimized out>, pp_stack=0x7ffc3ef982a0, func=0x7f0b8a0771d0) at ../Python/ceval.c:4690 python-greenlet#25 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3052 python-greenlet#26 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a04bf30, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#27 0x0000556bdc325b9b in function_call (func=0x7f0b8a0772d0, arg=arg@entry=0x7f0b8a3bd550, kw=kw@entry=0x0) at ../Objects/funcobject.c:523 python-greenlet#28 0x0000556bdc33f553 in PyObject_Call (kw=0x0, arg=0x7f0b8a3bd550, func=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#29 instancemethod_call (func=<optimized out>, func@entry=0x7f0b8a3c6a00, arg=0x7f0b8a3bd550, kw=0x0) at ../Objects/classobject.c:2600 python-greenlet#30 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a3c6a00, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#31 0x0000556bdc37eff8 in slot_tp_call (self=self@entry=0x7f0b8a03cfd0, args=0x7f0b8a044210, kwds=0x0) at ../Objects/typeobject.c:5609 python-greenlet#32 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a03cfd0, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#33 0x0000556bdc3128dd in do_call (nk=<optimized out>, na=<optimized out>, pp_stack=0x7ffc3ef98798, func=0x7f0b8a03cfd0) at ../Python/ceval.c:4593 python-greenlet#34 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef98798) at ../Python/ceval.c:4398 python-greenlet#35 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#36 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d530, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a076ee8, defcount=1, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#37 0x0000556bdc325cc2 in function_call (func=0x7f0b8a07f950, arg=0x7f0b8a3bd780, kw=0x7f0b8a042cb0) at ../Objects/funcobject.c:523 python-greenlet#38 0x0000556bdc30fa96 in PyObject_Call (kw=0x7f0b8a042cb0, arg=0x7f0b8a3bd780, func=0x7f0b8a07f950) at ../Objects/abstract.c:2544 python-greenlet#39 ext_do_call (nk=<optimized out>, na=1, flags=<optimized out>, pp_stack=0x7ffc3ef989d0, func=0x7f0b8a07f950) at ../Python/ceval.c:4690 python-greenlet#40 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3052 python-greenlet#41 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d2b0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#42 0x0000556bdc325b9b in function_call (func=0x7f0b8a07f850, arg=arg@entry=0x7f0b8a3bd730, kw=kw@entry=0x0) at ../Objects/funcobject.c:523 python-greenlet#43 0x0000556bdc33f553 in PyObject_Call (kw=0x0, arg=0x7f0b8a3bd730, func=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#44 instancemethod_call (func=<optimized out>, func@entry=0x7f0b8a427190, arg=0x7f0b8a3bd730, kw=0x0) at ../Objects/classobject.c:2600 python-greenlet#45 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a427190, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#46 0x0000556bdc37eff8 in slot_tp_call (self=self@entry=0x7f0b8a03cf90, args=0x7f0b8a044150, kwds=0x0) at ../Objects/typeobject.c:5609 python-greenlet#47 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a03cf90, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#48 0x0000556bdc3128dd in do_call (nk=<optimized out>, na=<optimized out>, pp_stack=0x7ffc3ef98ec8, func=0x7f0b8a03cf90) at ../Python/ceval.c:4593 python-greenlet#49 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef98ec8) at ../Python/ceval.c:4398 python-greenlet#50 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#51 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d530, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a076ee8, defcount=1, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#52 0x0000556bdc325cc2 in function_call (func=0x7f0b8a07f950, arg=0x7f0b8a3bdaf0, kw=0x7f0b8a042b90) at ../Objects/funcobject.c:523 python-greenlet#53 0x0000556bdc30fa96 in PyObject_Call (kw=0x7f0b8a042b90, arg=0x7f0b8a3bdaf0, func=0x7f0b8a07f950) at ../Objects/abstract.c:2544 python-greenlet#54 ext_do_call (nk=<optimized out>, na=1, flags=<optimized out>, pp_stack=0x7ffc3ef99100, func=0x7f0b8a07f950) at ../Python/ceval.c:4690 python-greenlet#55 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3052 python-greenlet#56 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d2b0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#57 0x0000556bdc325b9b in function_call (func=0x7f0b8a07f850, arg=arg@entry=0x7f0b8a3bdb40, kw=kw@entry=0x0) at ../Objects/funcobject.c:523 python-greenlet#58 0x0000556bdc33f553 in PyObject_Call (kw=0x0, arg=0x7f0b8a3bdb40, func=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#59 instancemethod_call (func=<optimized out>, func@entry=0x7f0b8a3c6820, arg=0x7f0b8a3bdb40, kw=0x0) at ../Objects/classobject.c:2600 python-greenlet#60 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a3c6820, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#61 0x0000556bdc37eff8 in slot_tp_call (self=self@entry=0x7f0b8a033c50, args=0x7f0b8a03c0d0, kwds=0x0) at ../Objects/typeobject.c:5609 python-greenlet#62 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a033c50, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#63 0x0000556bdc3128dd in do_call (nk=<optimized out>, na=<optimized out>, pp_stack=0x7ffc3ef995f8, func=0x7f0b8a033c50) at ../Python/ceval.c:4593 python-greenlet#64 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef995f8) at ../Python/ceval.c:4398 python-greenlet#65 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#66 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d530, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a076ee8, defcount=1, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#67 0x0000556bdc325cc2 in function_call (func=0x7f0b8a07f950, arg=0x7f0b8a3bd500, kw=0x7f0b8a042a70) at ../Objects/funcobject.c:523 python-greenlet#68 0x0000556bdc30fa96 in PyObject_Call (kw=0x7f0b8a042a70, arg=0x7f0b8a3bd500, func=0x7f0b8a07f950) at ../Objects/abstract.c:2544 python-greenlet#69 ext_do_call (nk=<optimized out>, na=1, flags=<optimized out>, pp_stack=0x7ffc3ef99830, func=0x7f0b8a07f950) at ../Python/ceval.c:4690 python-greenlet#70 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3052 python-greenlet#71 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d2b0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#72 0x0000556bdc325b9b in function_call (func=0x7f0b8a07f850, arg=arg@entry=0x7f0b8a3bd9b0, kw=kw@entry=0x0) at ../Objects/funcobject.c:523 python-greenlet#73 0x0000556bdc33f553 in PyObject_Call (kw=0x0, arg=0x7f0b8a3bd9b0, func=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#74 instancemethod_call (func=<optimized out>, func@entry=0x7f0b8a4310a0, arg=0x7f0b8a3bd9b0, kw=0x0) at ../Objects/classobject.c:2600 python-greenlet#75 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a4310a0, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#76 0x0000556bdc37eff8 in slot_tp_call (self=self@entry=0x7f0b8a033c10, args=0x7f0b8a033fd0, kwds=0x0) at ../Objects/typeobject.c:5609 python-greenlet#77 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a033c10, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#78 0x0000556bdc3128dd in do_call (nk=<optimized out>, na=<optimized out>, pp_stack=0x7ffc3ef99d28, func=0x7f0b8a033c10) at ../Python/ceval.c:4593 python-greenlet#79 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef99d28) at ../Python/ceval.c:4398 python-greenlet#80 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#81 0x0000556bdc3125fe in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef99e38, func=0x7f0b8a01ecd0) at ../Python/ceval.c:4461 python-greenlet#82 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef99e38) at ../Python/ceval.c:4396 python-greenlet#83 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#84 0x0000556bdc3125fe in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef99f48, func=0x7f0b8a01edd0) at ../Python/ceval.c:4461 python-greenlet#85 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef99f48) at ../Python/ceval.c:4396 python-greenlet#86 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#87 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a081d30, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=1, defs=0x7f0b8a008ba8, defcount=10, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#88 0x0000556bdc325cc2 in function_call (func=0x7f0b8a00ddd0, arg=arg@entry=0x7f0b8a033610, kw=kw@entry=0x7f0b8a014a70) at ../Objects/funcobject.c:523 python-greenlet#89 0x0000556bdc33f553 in PyObject_Call (kw=0x7f0b8a014a70, arg=0x7f0b8a033610, func=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#90 instancemethod_call (func=<optimized out>, func@entry=0x7f0b8a427140, arg=0x7f0b8a033610, arg@entry=0x7f0b8a4e0050, kw=kw@entry=0x7f0b8a014a70) at ../Objects/classobject.c:2600 python-greenlet#91 0x0000556bdc33f10f in PyObject_Call (kw=0x7f0b8a014a70, arg=0x7f0b8a4e0050, func=0x7f0b8a427140) at ../Objects/abstract.c:2544 python-greenlet#92 slot_tp_init (self=self@entry=0x7f0b8a3ebb10, args=args@entry=0x7f0b8a4e0050, kwds=kwds@entry=0x7f0b8a014a70) at ../Objects/typeobject.c:5869 python-greenlet#93 0x0000556bdc2feb87 in type_call (type=<optimized out>, type@entry=0x556bdd2a85c0, args=0x7f0b8a4e0050, kwds=0x7f0b8a014a70) at ../Objects/typeobject.c:765 python-greenlet#94 0x0000556bdc2f8903 in PyObject_Call (func=0x556bdd2a85c0, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#95 0x0000556bdc3128dd in do_call (nk=<optimized out>, na=<optimized out>, pp_stack=0x7ffc3ef9a458, func=0x556bdd2a85c0) at ../Python/ceval.c:4593 python-greenlet#96 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef9a458) at ../Python/ceval.c:4398 python-greenlet#97 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#98 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a37d830, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#99 0x0000556bdc311886 in PyEval_EvalCode (locals=0x7f0b8a4ae170, globals=0x7f0b8a4ae170, co=<optimized out>) at ../Python/ceval.c:669 python-greenlet#100 exec_statement (locals=0x7f0b8a4ae170, globals=0x7f0b8a4ae170, prog=<optimized out>, f=<optimized out>) at ../Python/ceval.c:5093 python-greenlet#101 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:2122 python-greenlet#102 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a3ea4b0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a381068, defcount=5, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#103 0x0000556bdc312953 in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef9a818, func=0x7f0b8a37d8d0) at ../Python/ceval.c:4471 python-greenlet#104 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef9a818) at ../Python/ceval.c:4396 python-greenlet#105 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 python-greenlet#106 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a37d4b0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a379ce8, defcount=1, closure=0x0) at ../Python/ceval.c:3608 python-greenlet#107 0x0000556bdc325b9b in function_call (func=func@entry=0x7f0b8a37de50, arg=0x7f0b8a3b5be0, kw=0x0) at ../Objects/funcobject.c:523 python-greenlet#108 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a37de50, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 python-greenlet#109 0x0000556bdc3b87e1 in RunModule (module=<optimized out>, set_argv0=1) at ../Modules/main.c:197 python-greenlet#110 0x0000556bdc3a68ed in Py_Main (argc=<optimized out>, argv=<optimized out>) at ../Modules/main.c:592#111 0x00007f0b8a54bd0a in __libc_start_main (main=0x556bdc3a6530 <main>, argc=5, argv=0x7ffc3ef9ac08, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffc3ef9abf8) at ../csu/libc-start.c:308 python-greenlet#112 0x0000556bdc3a646a in _start ()
…opagate to another greenlet and corrupt memory Hello up there. While working on Pygolang I've faced the following problem: a C++ exception thrown in one greenlet, without any try/catch block on that greenlet, might become propagated and "handled" by try/catch block established at another greenlet that happened to switch to the greenlet in question. "Handled" comes in quotes because the program usually segfaults after that. I've also observed segfaults before exception reaches user-level try/catch block - internally in __cxa_throw. Both this issues are likely of the same origin - due to the fact that C-level stack of a greenlet does not start from scratch and starts from C-level stack of the program state when the greenlet is switched to the first time. We already have one test for C++ exception handling in test_cpp that @snaury initially added in d9cb12a. However in that test all greenlets that throw C++ exceptions also put try/catch at the top of their C-stack. In the problem, that I describe, and that added test reproduces, there is no intended top-level try/catch block in C-stack of the greenlet in question that throws. As the test shows the exception becomes propagated to switcher's greenlet context and the program then dies with SIGSEGV: test_exception_switch_and_throw (greenlet.tests.test_cpp.CPPTests) ... terminate called after throwing an instance of 'exception_t' C++ exception unexpectedly caught in g1 <-- NOTE FAIL ====================================================================== FAIL: test_exception_switch_and_throw (greenlet.tests.test_cpp.CPPTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/kirr/src/tools/py/gevent/greenlet/src/greenlet/tests/test_cpp.py", line 62, in test_exception_switch_and_throw (ret, sig, " (core dumped)" if core else "")) AssertionError: failed with ret=0 sig=11 (core dumped) The C-level backtrace from the dumped core is attached in Appendix I. There the program dies somehow after running the code from _test_extension_cpp.cpp module. However with the following dirty-patch the situation becomes more clear: --- a/src/greenlet/tests/_test_extension_cpp.cpp +++ b/src/greenlet/tests/_test_extension_cpp.cpp @@ -70,6 +70,7 @@ test_exception_switch(PyObject* self, PyObject* args) static PyObject* py_test_exception_throw(PyObject* self, PyObject* args) { + abort(); if (!PyArg_ParseTuple(args, "")) return NULL; p_test_exception_throw(0); The C-level backtrace of this abort when, run from under g2, shows that g2 C-stack starts from what was there before for first greenlet with try/catch block: #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50 #1 0x00007f8be31e9537 in __GI_abort () at abort.c:79 #2 0x00007f8be3529423 in py_test_exception_throw (self=0x0, args=0x7f8be317f050) at src/greenlet/tests/_test_extension_cpp.cpp:73 #3 0x00005584d0389903 in PyObject_Call (func=0x7f8be2cdd690, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #4 0x00007f8be3530d63 in g_initialstub (mark=0x7ffe6d1b1c88) at src/greenlet/greenlet.c:931 #5 0x00007f8be3530290 in g_switch (target=0x7f8be2cd6230, args=0x7f8be317f050, kwargs=0x0) at src/greenlet/greenlet.c:692 #6 0x00007f8be35329e2 in PyGreenlet_Switch (g=0x7f8be2cd6230, args=0x7f8be317f050, kwargs=0x0) at src/greenlet/greenlet.c:1806 <- first switch to G2 #7 0x00007f8be35294da in test_exception_switch_and_do_in_g2 (self=0x0, args=0x7f8be2ce33d0) <- this code runs in G1 at src/greenlet/tests/_test_extension_cpp.cpp:105 #8 0x00005584d039de7a in call_function (oparg=<optimized out>, pp_stack=0x7ffe6d1b1e28) at ../Python/ceval.c:4376 #9 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #10 0x00005584d039c3cc in PyEval_EvalCodeEx (co=0x7f8be2cd9cb0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 #11 0x00005584d03b6b9b in function_call (func=func@entry=0x7f8be2cde650, arg=0x7f8be317f050, kw=0x0) at ../Objects/funcobject.c:523 #12 0x00005584d0389903 in PyObject_Call (func=0x7f8be2cde650, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #13 0x00007f8be3530d63 in g_initialstub (mark=0x7ffe6d1b20b8) at src/greenlet/greenlet.c:931 #14 0x00007f8be3530290 in g_switch (target=0x7f8be2cd6410, args=0x7f8be317f050, kwargs=0x0) at src/greenlet/greenlet.c:692 #15 0x00007f8be3531c4c in green_switch (self=0x7f8be2cd6410, args=0x7f8be317f050, kwargs=0x0) at src/greenlet/greenlet.c:1321 <- switch to G1 ... The problem might be worked-around with putting try/catch on C-stack for every greenlet, but a more proper fix would be to unlink return address and C-stack-frame of created greenlet from other greenlets completely (i.e. rewrite first frame of C-stack for created greenlet to return to NULL). Thanks beforehand, Kirill P.S. The problem described at #197 (comment) and #205 might be related to hereby issue (/cc @ThePrez, @kadler, @jamadden). P.P.S. The test does not fail on master. I see there @jamadden switched the codebase to C++ almost completely and there is some explicit attempts to save/restore exception state added in 87edf955. However that exception save/restore is specific to Win32 and the test succeeds for me on Linux. I still see the following comment in UserGreenlet::inner_bootstrap // C++ exceptions cannot propagate to the parent greenlet from // here. (TODO: Do we need a catch(...) clause, perhaps on the // function itself? ALl we could do is terminate the program.) ( https://github.com/python-greenlet/greenlet/blob/3e534d6b/src/greenlet/greenlet.cpp#L1085-L1104 ) probably this indeed works only by luck, or due to G_NOEXCEPT modifier of UserGreenlet::inner_bootstrap upon seeing which __cxa_throw stops unwinding C-stack and just calls std::terminate. -------- Appendix I. C-level backtrace after the test died with SIGSEGV #0 0x0000556bdc30d394 in PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3351 #1 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a03acb0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 #2 0x0000556bdc325b9b in function_call (func=func@entry=0x7f0b8a03f650, arg=0x7f0b8a4e0050, kw=0x0) at ../Objects/funcobject.c:523 #3 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a03f650, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #4 0x00007f0b8a891d63 in g_initialstub (mark=0x7ffc3ef97988) at src/greenlet/greenlet.c:931 #5 0x00007f0b8a891290 in g_switch (target=0x7f0b8a037410, args=0x7f0b8a4e0050, kwargs=0x0) at src/greenlet/greenlet.c:692 #6 0x00007f0b8a892c4c in green_switch (self=0x7f0b8a037410, args=0x7f0b8a4e0050, kwargs=0x0) at src/greenlet/greenlet.c:1321 #7 0x0000556bdc30ce7a in call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef97ac8) at ../Python/ceval.c:4376 #8 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #9 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a03ad30, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 #10 0x0000556bdc312953 in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef97ca8, func=0x7f0b8a03aed0) at ../Python/ceval.c:4471 #11 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef97ca8) at ../Python/ceval.c:4396 #12 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #13 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a03ac30, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 #14 0x0000556bdc312953 in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef97e88, func=0x7f0b8a03af50) at ../Python/ceval.c:4471 #15 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef97e88) at ../Python/ceval.c:4396 #16 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #17 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a03adb0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 #18 0x0000556bdc312953 in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef98068, func=0x7f0b8a03f250) at ../Python/ceval.c:4471 #19 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef98068) at ../Python/ceval.c:4396 #20 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #21 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a04bd30, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a0752a8, defcount=1, closure=0x0) at ../Python/ceval.c:3608 #22 0x0000556bdc325cc2 in function_call (func=0x7f0b8a0771d0, arg=0x7f0b8a3bd5a0, kw=0x7f0b8a042dd0) at ../Objects/funcobject.c:523 #23 0x0000556bdc30fa96 in PyObject_Call (kw=0x7f0b8a042dd0, arg=0x7f0b8a3bd5a0, func=0x7f0b8a0771d0) at ../Objects/abstract.c:2544 #24 ext_do_call (nk=<optimized out>, na=1, flags=<optimized out>, pp_stack=0x7ffc3ef982a0, func=0x7f0b8a0771d0) at ../Python/ceval.c:4690 #25 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3052 #26 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a04bf30, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 #27 0x0000556bdc325b9b in function_call (func=0x7f0b8a0772d0, arg=arg@entry=0x7f0b8a3bd550, kw=kw@entry=0x0) at ../Objects/funcobject.c:523 #28 0x0000556bdc33f553 in PyObject_Call (kw=0x0, arg=0x7f0b8a3bd550, func=<optimized out>) at ../Objects/abstract.c:2544 #29 instancemethod_call (func=<optimized out>, func@entry=0x7f0b8a3c6a00, arg=0x7f0b8a3bd550, kw=0x0) at ../Objects/classobject.c:2600 #30 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a3c6a00, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #31 0x0000556bdc37eff8 in slot_tp_call (self=self@entry=0x7f0b8a03cfd0, args=0x7f0b8a044210, kwds=0x0) at ../Objects/typeobject.c:5609 #32 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a03cfd0, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #33 0x0000556bdc3128dd in do_call (nk=<optimized out>, na=<optimized out>, pp_stack=0x7ffc3ef98798, func=0x7f0b8a03cfd0) at ../Python/ceval.c:4593 #34 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef98798) at ../Python/ceval.c:4398 #35 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #36 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d530, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a076ee8, defcount=1, closure=0x0) at ../Python/ceval.c:3608 #37 0x0000556bdc325cc2 in function_call (func=0x7f0b8a07f950, arg=0x7f0b8a3bd780, kw=0x7f0b8a042cb0) at ../Objects/funcobject.c:523 #38 0x0000556bdc30fa96 in PyObject_Call (kw=0x7f0b8a042cb0, arg=0x7f0b8a3bd780, func=0x7f0b8a07f950) at ../Objects/abstract.c:2544 #39 ext_do_call (nk=<optimized out>, na=1, flags=<optimized out>, pp_stack=0x7ffc3ef989d0, func=0x7f0b8a07f950) at ../Python/ceval.c:4690 #40 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3052 #41 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d2b0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 #42 0x0000556bdc325b9b in function_call (func=0x7f0b8a07f850, arg=arg@entry=0x7f0b8a3bd730, kw=kw@entry=0x0) at ../Objects/funcobject.c:523 #43 0x0000556bdc33f553 in PyObject_Call (kw=0x0, arg=0x7f0b8a3bd730, func=<optimized out>) at ../Objects/abstract.c:2544 #44 instancemethod_call (func=<optimized out>, func@entry=0x7f0b8a427190, arg=0x7f0b8a3bd730, kw=0x0) at ../Objects/classobject.c:2600 #45 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a427190, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #46 0x0000556bdc37eff8 in slot_tp_call (self=self@entry=0x7f0b8a03cf90, args=0x7f0b8a044150, kwds=0x0) at ../Objects/typeobject.c:5609 #47 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a03cf90, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #48 0x0000556bdc3128dd in do_call (nk=<optimized out>, na=<optimized out>, pp_stack=0x7ffc3ef98ec8, func=0x7f0b8a03cf90) at ../Python/ceval.c:4593 #49 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef98ec8) at ../Python/ceval.c:4398 #50 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #51 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d530, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a076ee8, defcount=1, closure=0x0) at ../Python/ceval.c:3608 #52 0x0000556bdc325cc2 in function_call (func=0x7f0b8a07f950, arg=0x7f0b8a3bdaf0, kw=0x7f0b8a042b90) at ../Objects/funcobject.c:523 #53 0x0000556bdc30fa96 in PyObject_Call (kw=0x7f0b8a042b90, arg=0x7f0b8a3bdaf0, func=0x7f0b8a07f950) at ../Objects/abstract.c:2544 #54 ext_do_call (nk=<optimized out>, na=1, flags=<optimized out>, pp_stack=0x7ffc3ef99100, func=0x7f0b8a07f950) at ../Python/ceval.c:4690 #55 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3052 #56 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d2b0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 #57 0x0000556bdc325b9b in function_call (func=0x7f0b8a07f850, arg=arg@entry=0x7f0b8a3bdb40, kw=kw@entry=0x0) at ../Objects/funcobject.c:523 #58 0x0000556bdc33f553 in PyObject_Call (kw=0x0, arg=0x7f0b8a3bdb40, func=<optimized out>) at ../Objects/abstract.c:2544 #59 instancemethod_call (func=<optimized out>, func@entry=0x7f0b8a3c6820, arg=0x7f0b8a3bdb40, kw=0x0) at ../Objects/classobject.c:2600 #60 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a3c6820, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #61 0x0000556bdc37eff8 in slot_tp_call (self=self@entry=0x7f0b8a033c50, args=0x7f0b8a03c0d0, kwds=0x0) at ../Objects/typeobject.c:5609 #62 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a033c50, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #63 0x0000556bdc3128dd in do_call (nk=<optimized out>, na=<optimized out>, pp_stack=0x7ffc3ef995f8, func=0x7f0b8a033c50) at ../Python/ceval.c:4593 #64 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef995f8) at ../Python/ceval.c:4398 #65 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #66 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d530, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a076ee8, defcount=1, closure=0x0) at ../Python/ceval.c:3608 #67 0x0000556bdc325cc2 in function_call (func=0x7f0b8a07f950, arg=0x7f0b8a3bd500, kw=0x7f0b8a042a70) at ../Objects/funcobject.c:523 #68 0x0000556bdc30fa96 in PyObject_Call (kw=0x7f0b8a042a70, arg=0x7f0b8a3bd500, func=0x7f0b8a07f950) at ../Objects/abstract.c:2544 #69 ext_do_call (nk=<optimized out>, na=1, flags=<optimized out>, pp_stack=0x7ffc3ef99830, func=0x7f0b8a07f950) at ../Python/ceval.c:4690 #70 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3052 #71 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a07d2b0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 #72 0x0000556bdc325b9b in function_call (func=0x7f0b8a07f850, arg=arg@entry=0x7f0b8a3bd9b0, kw=kw@entry=0x0) at ../Objects/funcobject.c:523 #73 0x0000556bdc33f553 in PyObject_Call (kw=0x0, arg=0x7f0b8a3bd9b0, func=<optimized out>) at ../Objects/abstract.c:2544 #74 instancemethod_call (func=<optimized out>, func@entry=0x7f0b8a4310a0, arg=0x7f0b8a3bd9b0, kw=0x0) at ../Objects/classobject.c:2600 #75 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a4310a0, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #76 0x0000556bdc37eff8 in slot_tp_call (self=self@entry=0x7f0b8a033c10, args=0x7f0b8a033fd0, kwds=0x0) at ../Objects/typeobject.c:5609 #77 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a033c10, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #78 0x0000556bdc3128dd in do_call (nk=<optimized out>, na=<optimized out>, pp_stack=0x7ffc3ef99d28, func=0x7f0b8a033c10) at ../Python/ceval.c:4593 #79 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef99d28) at ../Python/ceval.c:4398 #80 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #81 0x0000556bdc3125fe in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef99e38, func=0x7f0b8a01ecd0) at ../Python/ceval.c:4461 #82 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef99e38) at ../Python/ceval.c:4396 #83 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #84 0x0000556bdc3125fe in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef99f48, func=0x7f0b8a01edd0) at ../Python/ceval.c:4461 #85 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef99f48) at ../Python/ceval.c:4396 #86 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #87 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a081d30, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=1, defs=0x7f0b8a008ba8, defcount=10, closure=0x0) at ../Python/ceval.c:3608 #88 0x0000556bdc325cc2 in function_call (func=0x7f0b8a00ddd0, arg=arg@entry=0x7f0b8a033610, kw=kw@entry=0x7f0b8a014a70) at ../Objects/funcobject.c:523 #89 0x0000556bdc33f553 in PyObject_Call (kw=0x7f0b8a014a70, arg=0x7f0b8a033610, func=<optimized out>) at ../Objects/abstract.c:2544 #90 instancemethod_call (func=<optimized out>, func@entry=0x7f0b8a427140, arg=0x7f0b8a033610, arg@entry=0x7f0b8a4e0050, kw=kw@entry=0x7f0b8a014a70) at ../Objects/classobject.c:2600 #91 0x0000556bdc33f10f in PyObject_Call (kw=0x7f0b8a014a70, arg=0x7f0b8a4e0050, func=0x7f0b8a427140) at ../Objects/abstract.c:2544 #92 slot_tp_init (self=self@entry=0x7f0b8a3ebb10, args=args@entry=0x7f0b8a4e0050, kwds=kwds@entry=0x7f0b8a014a70) at ../Objects/typeobject.c:5869 #93 0x0000556bdc2feb87 in type_call (type=<optimized out>, type@entry=0x556bdd2a85c0, args=0x7f0b8a4e0050, kwds=0x7f0b8a014a70) at ../Objects/typeobject.c:765 #94 0x0000556bdc2f8903 in PyObject_Call (func=0x556bdd2a85c0, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #95 0x0000556bdc3128dd in do_call (nk=<optimized out>, na=<optimized out>, pp_stack=0x7ffc3ef9a458, func=0x556bdd2a85c0) at ../Python/ceval.c:4593 #96 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef9a458) at ../Python/ceval.c:4398 #97 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #98 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a37d830, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x0, defcount=0, closure=0x0) at ../Python/ceval.c:3608 #99 0x0000556bdc311886 in PyEval_EvalCode (locals=0x7f0b8a4ae170, globals=0x7f0b8a4ae170, co=<optimized out>) at ../Python/ceval.c:669 #100 exec_statement (locals=0x7f0b8a4ae170, globals=0x7f0b8a4ae170, prog=<optimized out>, f=<optimized out>) at ../Python/ceval.c:5093 #101 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:2122 #102 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a3ea4b0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a381068, defcount=5, closure=0x0) at ../Python/ceval.c:3608 #103 0x0000556bdc312953 in fast_function (nk=<optimized out>, na=<optimized out>, n=<optimized out>, pp_stack=0x7ffc3ef9a818, func=0x7f0b8a37d8d0) at ../Python/ceval.c:4471 #104 call_function (oparg=<optimized out>, pp_stack=0x7ffc3ef9a818) at ../Python/ceval.c:4396 #105 PyEval_EvalFrameEx (f=<optimized out>, throwflag=<optimized out>) at ../Python/ceval.c:3013 #106 0x0000556bdc30b3cc in PyEval_EvalCodeEx (co=0x7f0b8a37d4b0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kws=<optimized out>, kwcount=0, defs=0x7f0b8a379ce8, defcount=1, closure=0x0) at ../Python/ceval.c:3608 #107 0x0000556bdc325b9b in function_call (func=func@entry=0x7f0b8a37de50, arg=0x7f0b8a3b5be0, kw=0x0) at ../Objects/funcobject.c:523 #108 0x0000556bdc2f8903 in PyObject_Call (func=0x7f0b8a37de50, arg=<optimized out>, kw=<optimized out>) at ../Objects/abstract.c:2544 #109 0x0000556bdc3b87e1 in RunModule (module=<optimized out>, set_argv0=1) at ../Modules/main.c:197 #110 0x0000556bdc3a68ed in Py_Main (argc=<optimized out>, argv=<optimized out>) at ../Modules/main.c:592#111 0x00007f0b8a54bd0a in __libc_start_main (main=0x556bdc3a6530 <main>, argc=5, argv=0x7ffc3ef9ac08, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffc3ef9abf8) at ../csu/libc-start.c:308 #112 0x0000556bdc3a646a in _start ()
First, a fairy story: I have a very big system based on greenlet at work, with dozens of plugins and thousands of greenlets, all in one process. Occasionaly, I notice that my system stalls with severe lags, where some instances of plugins might wait minutes in line for their turn to run. It's quite obvious that one of plugins contains a bug, likely doing something blocking, or maybe computing something for a bit too long. If only it was possible to know which plugin/function consumes CPU cycles the most!
As a side note: usual profiling tools don't work correctly. Others use hacks to detect greenlet switches, with workarounds for specific event libraries (e.g. gevent), and are not optimal. The most problematic case is when switch goes into C code. Python's settrace only traces on Python code, any switch to C code is invisible until trace resurfaces back in Python code, in some other greenlet, unknown time later.
I propose a new global function set_switch_callback that accepts a callback object as a parameter and returns previous callback object (None initially). The calback value of None would mean no callback is called. The callback would execute immediately before an actual C-level switch or throw with two parameters: current greenlet and target greenlet objects. The callback is only called when those differ, obviously. The callback is only set for the current thread, making it thread-safe and similar to settrace. Any exceptions raised by the callback will be raised by switch/throw instead of the intended action.
This way tracing and profiling code would be able to have a definite information about greenlet switches (and thus would be able to time them), even when switches are made between greenlets in C.
What do you think?
The text was updated successfully, but these errors were encountered: