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

Potential memory leak #7

Closed
ediskandarov opened this issue Oct 19, 2021 · 13 comments · Fixed by #8
Closed

Potential memory leak #7

ediskandarov opened this issue Oct 19, 2021 · 13 comments · Fixed by #8

Comments

@ediskandarov
Copy link
Contributor

token: Token = event_store.set(deque())
try:
await self.app(scope, receive, send)
finally:
await self._process_events()
event_store.reset(token)

        token: Token = event_store.set(deque())
        try:
            await self.app(scope, receive, send)
        finally:
            await self._process_events()
        event_store.reset(token)

If await self._process_events() raises an exception then event_store.reset(token) does not happen.

@ediskandarov
Copy link
Contributor Author

@melvinkcx
Copy link
Owner

melvinkcx commented Oct 19, 2021

Thanks for bringing this to my attention!

Went through the link you provided above, and one of the references:
https://stackoverflow.com/questions/66856654/what-happens-if-i-dont-reset-pythons-contextvars

It doesn't seem like a memory leak can occur even if self._process_events() raises an Exception after your PR #6. The token is now declared as a local variable within the scope of __call__(). It won't and can't be referenced after the execution of __call__(). -> Hence, will be GC'd

@melvinkcx
Copy link
Owner

melvinkcx commented Oct 19, 2021

Your PR #8 will change the intended behaviour of the event processing. If both await self.app(scope, receive, send) and await self._process_events() are wrapped within the same try block, any exceptions raised during the request processing will also mean no events will be handled.

@ediskandarov
Copy link
Contributor Author

perhaps, a nested try block would be a safe approach here?

@melvinkcx
Copy link
Owner

Though, I'm really curious to perform some sort of tests to verify both of our hypothesis

@ediskandarov
Copy link
Contributor Author

it should either rely on the garbage collector and does not care about context var reset at all, or come up with a robust context var management technique

@melvinkcx
Copy link
Owner

Yes, a nested try...finally block could be the solution.

@melvinkcx
Copy link
Owner

I agree with you, since we're using .reset() here, we should do it properly instead of relying on GC to clean it up in case of any exceptions

@ediskandarov
Copy link
Contributor Author

here's an example inspired by loguru's logger

    @contextlib.contextmanager
    def contextualize():
        token = context.set(deque())

        try:
            yield
        finally:
            context.reset(token)

and then use this context manager in the middleware

@melvinkcx
Copy link
Owner

I like this solution, we can potentially do:

with event_store_ctx():
    try:
         await self.app(scope, receive, send)
    finally:
         await self._process_events()

@ediskandarov
Copy link
Contributor Author

yeah, something like that

@melvinkcx
Copy link
Owner

Feel free to use a context manager. If not, we can stick to the nested try block for now.
I can implement the context manager afterwards =)

Again, thanks for your contribution!

@ediskandarov
Copy link
Contributor Author

I've updated pull request

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 a pull request may close this issue.

2 participants