Skip to content
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

Fix several greenlet leaks #261

Merged
merged 65 commits into from Oct 13, 2021
Merged

Fix several greenlet leaks #261

merged 65 commits into from Oct 13, 2021

Conversation

jamadden
Copy link
Contributor

Most commonly when threads are involved. This fixes #252 (and probably gevent/gevent#1795) without any user code changes. (There are at least 6 tests that leak greenlets in 1.1.2, including older tests not specifically written to leak; no tests leak greenlets in this branch.)

This moves to thread-local variables for most things instead of module-global variables that have to be constantly checked to be sure they're valid. This requires a compiler that supports that; all tested compilers/platforms do, but it may end up dropping support for some very old platforms. (If that winds up mattering, it can be worked around at a speed cost similar to the way Python 2.7 on Windows is working.)

The core of the fix is to eliminate an uncollectable reference cycle/chain: greenlets no longer reference their thread-state dictionary directly. This results in an API and ABI difference for C subclasses of greenlets, so this will need to be version 2.0. I'd like to take advantage of that to address some other things for 2.0 as well. (Part of the fix is O(n) when a thread that uses greenlets dies, so for environments where there are many GCable objects, and many threads rapidly starting and ending, but a small memory leak is acceptable, there should be a way to disable this part of the fix — plus provide some visibility into it so we know whether it really is a bottleneck.)

I've built and tested gevent against this branch with no issues.

In the single-threaded case, this is very slightly slower than 1.1.2; once some more code cleanups are done I will see if we can get back some of that performance.

Benchmark gl-1.1.2 tl-pr
create a greenlet 68.7 ns 75.1 ns: 1.09x slower
switch between two greenlets 301 ns 332 ns: 1.10x slower
getcurrent single thread 17.9 ns 22.8 ns: 1.27x slower
chain(100000) 256 ms 267 ms: 1.04x slower

Thread-local C++ objects should be a portable way to get thread destructors.

The slp_* functions need to be compiled as C because we call them from assembly.

forward declarations should also be c linkage, otherwise MSVC complains.
This eliminates the complexity of green_updatecurrent, I believe. Much more can be simplified as well.

Still need to add the thread cleanup functions.
Currently, we're somewhat slower than the global variable case, as
expected.

| Benchmark                    | gl-1.1.2 | tl1                   |
|------------------------------|:--------:|:---------------------:|
| create a greenlet            | 68.7 ns  | 88.0 ns: 1.28x slower |
| switch between two greenlets | 301 ns   | 435 ns: 1.45x slower  |
| getcurrent single thread     | 17.9 ns  | 31.8 ns: 1.78x slower |
| chain(100000)                | 256 ms   | 275 ms: 1.08x slower  |
| Geometric mean               | (ref)    | 1.37x slower          |

Also some Windows debugging.
Amusingly, this only caused issues on 64-bit Python 3.9 and 3.10 an Windows.
…global in g_switchstack into a single copy, thus messing up the stack.

Try accessing it with the global variable each time.

This makes clang, at least, look it up each time, even with optimizations on.
We're now using Py_AddPendingCall to clean up some thread-local things that don't clean themselves up. I'm still working to be sure we break cycles.
You need special syntax to return a pointer in a noinline function in MSVC.

Because the parser ignores, without warning, __declspec after a * character.
https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=msvc-160
Improve the debugging to not do that. Also limit it to a suspected issue for now.
This should let us restore support for older compilers.
I haven't tested this on other platforms or with a more complex app like gevent yet though.
…s we take.

Also add a testing API to be sure cleanups have finished.
@jgehrcke
Copy link

I admire your dedication @jamadden. This was hard work!

And as always also great documentation work.

@jamadden
Copy link
Contributor Author

This still may not fix all leaks. In 1.1.2, the test run was leaking 26 greenlets; now its down to leaking 16. So there's improvement, but still work to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Killing greenlets across threads may leak greenlets and their stacks?
2 participants