Skip to content

feat: add autorestore_context option and make it True by default#20

Open
spumer wants to merge 3 commits into
gfmio:mainfrom
spumer:feat/autorestore-context
Open

feat: add autorestore_context option and make it True by default#20
spumer wants to merge 3 commits into
gfmio:mainfrom
spumer:feat/autorestore-context

Conversation

@spumer

@spumer spumer commented Apr 6, 2025

Copy link
Copy Markdown

The main reason for this behavior is we make usage of async_to_sync and sync_to_async is transparent.
Programmers can use it without any unexpected side effects.


So, i can wait for your PR #19 and adapt my changes.

We use your lib on production since 2023 and i wrote article about that:
https://habr.com/ru/companies/tochka/articles/798577/

We run sync handlers in fastapi through asyncio-gevent, and this save us! Thank you for this project!

Summary by Sourcery

Add context preservation functionality to async and sync function conversions in asyncio-gevent

New Features:

  • Introduce an autorestore_context option that preserves context variables when converting between sync and async functions
  • Add support for passing and restoring context variables across greenlet and future conversions

Enhancements:

  • Modify async_to_sync, sync_to_async, and greenlet_to_future functions to support context variable preservation
  • Improve context handling to make async and sync function conversions more transparent

Tests:

  • Add test cases to verify context preservation for both sync_to_async and async_to_sync function conversions

@cr-gpt

cr-gpt Bot commented Apr 6, 2025

Copy link
Copy Markdown

Seems you are using me but didn't get OPENAI_API_KEY seted in Variables/Secrets for this repo. you could follow readme for more information

@sourcery-ai

sourcery-ai Bot commented Apr 6, 2025

Copy link
Copy Markdown

Reviewer's Guide by Sourcery

This pull request introduces an autorestore_context option to greenlet_to_future, async_to_sync, and sync_to_async functions. This option, enabled by default, ensures that context variables are properly passed and restored when switching between asyncio and gevent, providing more transparent usage of async_to_sync and sync_to_async.

Sequence diagram for sync_to_async with autorestore_context

sequenceDiagram
    participant SyncFn as Synchronous Function
    participant geventGreenlet as gevent.Greenlet
    participant greenlet_to_future as greenlet_to_future
    participant asyncioEventLoop as asyncio Event Loop
    participant Future as asyncio.Future

    SyncFn->>geventGreenlet: gevent.Greenlet(fn, *args, **kwargs)
    activate geventGreenlet
    geventGreenlet->>greenlet_to_future: greenlet_to_future(greenlet, autorestore_context=True)
    activate greenlet_to_future
    greenlet_to_future->>asyncioEventLoop: loop.run_in_executor(fn)
    activate asyncioEventLoop
    asyncioEventLoop-->>greenlet_to_future: Result from fn
    deactivate asyncioEventLoop
    greenlet_to_future->>Future: Resolves with result
    deactivate greenlet_to_future
    Future-->>geventGreenlet: Result
    geventGreenlet-->>SyncFn: Result
    deactivate geventGreenlet

    opt autorestore_context is True
        geventGreenlet->>SyncFn: Restore context variables
    end
Loading

Sequence diagram for async_to_sync with autorestore_context

sequenceDiagram
    participant AsyncCoroutine as Asynchronous Coroutine
    participant future_to_greenlet as future_to_greenlet
    participant geventGreenlet as gevent.Greenlet
    participant asyncioEventLoop as asyncio Event Loop
    participant Future as asyncio.Future

    AsyncCoroutine->>future_to_greenlet: future_to_greenlet(coroutine(), autorestore_context=True)
    activate future_to_greenlet
    future_to_greenlet->>asyncioEventLoop: loop.create_task(coroutine())
    activate asyncioEventLoop
    asyncioEventLoop-->>future_to_greenlet: Future
    deactivate asyncioEventLoop
    future_to_greenlet->>geventGreenlet: gevent.Greenlet(run_until_complete, future)
    activate geventGreenlet
    geventGreenlet->>Future: Await Future
    Future-->>geventGreenlet: Result
    deactivate geventGreenlet
    future_to_greenlet-->>AsyncCoroutine: Result
    deactivate future_to_greenlet

    opt autorestore_context is True
        geventGreenlet->>AsyncCoroutine: Restore context variables
    end
Loading

File-Level Changes

Change Details Files
Introduces an autorestore_context option to greenlet_to_future, async_to_sync, and sync_to_async functions, enabled by default, to automatically pass and restore context variables when switching between asyncio and gevent.
  • Added autorestore_context parameter to greenlet_to_future, async_to_sync, and sync_to_async functions.
  • Modified greenlet_to_future to copy the context and restore it after execution.
  • Modified async_to_sync to copy the context and restore it after execution.
  • Modified sync_to_async to copy the context and restore it after execution.
  • Added tests to verify context passing and restoration for async_to_sync and sync_to_async.
tests/test_asyncio_on_gevent.py
asyncio_gevent/greenlet_to_future.py
asyncio_gevent/future_to_greenlet.py
asyncio_gevent/async_to_sync.py
asyncio_gevent/sync_to_async.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!
  • Generate a plan of action for an issue: Comment @sourcery-ai plan on
    an issue to generate a plan of action for it.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

sourcery-ai[bot]
sourcery-ai Bot previously approved these changes Apr 6, 2025

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @spumer - I've reviewed your changes - here's some feedback:

Overall Comments:

  • Consider adding a helper function to avoid repeating the context restoration logic in async_to_sync and greenlet_to_future.
  • The added tests are great for demonstrating the context preservation, but consider adding tests that verify context isolation between greenlets.
Here's what I looked at during the review
  • 🟡 General issues: 4 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Complexity: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread asyncio_gevent/greenlet_to_future.py Outdated
Comment thread asyncio_gevent/greenlet_to_future.py
Comment thread asyncio_gevent/future_to_greenlet.py Outdated
Comment thread asyncio_gevent/greenlet_to_future.py
@spumer

spumer commented Apr 15, 2025

Copy link
Copy Markdown
Author

@gfmio can you review too?)

@gfmio gfmio requested a review from Copilot July 10, 2025 15:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds an autorestore_context option (enabled by default) to preserve context variables when converting between sync and async functions, making async_to_sync, sync_to_async, and related internals context‐aware and transparent.

  • Introduce autorestore_context parameter in sync_to_async and async_to_sync and propagate it to greenlet_to_future
  • Capture, pass, and restore contextvars in _await_greenlet and future_to_greenlet
  • Add tests to verify context preservation across greenlet/future boundaries

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_asyncio_on_gevent.py Add tests for context preservation in sync_to_async and async_to_sync
asyncio_gevent/sync_to_async.py Added autorestore_context parameter and forwarded it to greenlet_to_future
asyncio_gevent/greenlet_to_future.py Capture contextvars before start, restore after await, updated signature and executor
asyncio_gevent/future_to_greenlet.py Propagate gr_context when scheduling coroutine futures
asyncio_gevent/async_to_sync.py Added autorestore_context parameter and restore context after sync call
Comments suppressed due to low confidence (3)

asyncio_gevent/greenlet_to_future.py:82

  • The return type of greenlet_to_future was changed from asyncio.Future to typing.Awaitable, which may be a breaking API change. Consider documenting this change or providing backward compatibility.
) -> typing.Awaitable:

tests/test_asyncio_on_gevent.py:97

  • [nitpick] Test function name is verbose and uses multiple underscores; consider renaming to test_sync_to_async_context_preservation for clarity.
def test_asyncio_on_gevent___sync_to_async__pass_and_copyback_context():

tests/test_asyncio_on_gevent.py:1

  • The tests reference asyncio_gevent but there is no import asyncio_gevent statement. Add import asyncio_gevent at the top to avoid NameError.
import contextvars

Comment thread asyncio_gevent/future_to_greenlet.py Outdated
Comment thread asyncio_gevent/greenlet_to_future.py Outdated
@gfmio

gfmio commented Jul 10, 2025

Copy link
Copy Markdown
Owner

Thank you for using asyncio-gevent, I'm glad you find it useful! Thank you also for your PR and sorry for the wait.

I've just had a quick look and I think the PR looks good to me (Edit: there is one deadlock issue that requires addressing). I agree that adding context restoration is important.

I tried to merge in main, but there are a few minor merge conflicts.

Can you please address those and do the deduplication the bot reviewers have highlighted?

Once that's taken care of, I'll merge in your PR and cut version 0.3.0.

Thanks!

@gfmio gfmio left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please address the two issues? The potential deadlock issue is a definite blocker as it stands.

Comment thread asyncio_gevent/future_to_greenlet.py Outdated
Comment thread asyncio_gevent/greenlet_to_future.py Outdated
Святослав Посохин and others added 3 commits March 27, 2026 18:01
The main reason for this behavior is we make usage of async_to_sync and sync_to_async is transparent.
    Programmers can use it without any _unexpected_ side effects.
… parameter

Remove global ThreadPoolExecutor(max_workers=1) in greenlet_to_future that
caused deadlocks with concurrent calls. Add optional executor parameter
(default None = asyncio default executor). Extract _ensure_future helper
in future_to_greenlet to deduplicate coroutine scheduling logic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@spumer

spumer commented Mar 27, 2026

Copy link
Copy Markdown
Author

@gfmio deadlock tests implemented and i found a way to exclude ThreadPoolExecutor from implementation.

loop.call_soon / direct call -> loop.call_soon_threadsafe

This allow switch between EvenLoop greenlet and otherone, like between threads. So, detailed overview in module docstring.

This allow us use "await future" without locking forever.

You can replace call_soon_threadsafe back to direct call / call_soon and re-run tests, you will got a deadlock.

@spumer

spumer commented Mar 27, 2026

Copy link
Copy Markdown
Author

Before that call_soon_threadsafe was called in ThreadPoolExecutor (greenlet.join).

When a spawned greenlet completes, TWO events occur in parallel:

Path 1 (link callback):
greenlet dies → link → future.set_result()
→ call_soon(__wakeup) → in _ready, BUT WITHOUT _write_to_self ✗

Path 2 (executor):
greenlet dies → greenlet.join() returns → executor worker completes
→ concurrent.futures.Future resolves
→ asyncio.wrap_future → call_soon_threadsafe(...)
→ _write_to_self() → self-pipe → selector wakes up! ✓

UPD: good article about asyncio event loop and self-pipe mechanics: https://habr.com/ru/articles/995032/

@spumer

spumer commented Apr 8, 2026

Copy link
Copy Markdown
Author

Can we run tests or review via AI?

@spumer spumer requested a review from gfmio April 10, 2026 09:55
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.

3 participants