Skip to content

provide sniffio.thread_local.name for coroutine libraries to set#23

Merged
graingert merged 12 commits intopython-trio:masterfrom
graingert:provide-thread-local
Apr 9, 2021
Merged

provide sniffio.thread_local.name for coroutine libraries to set#23
graingert merged 12 commits intopython-trio:masterfrom
graingert:provide-thread-local

Conversation

@graingert
Copy link
Copy Markdown
Member

@graingert graingert commented Mar 8, 2021

No description provided.

@graingert graingert force-pushed the provide-thread-local branch from 1154f58 to 8050862 Compare March 8, 2021 23:07
@graingert graingert force-pushed the provide-thread-local branch from 8050862 to 9bb6ca2 Compare March 8, 2021 23:09
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 8, 2021

Codecov Report

Merging #23 (a0f49cb) into master (5ef8e9d) will not change coverage.
The diff coverage is 100.00%.

@@            Coverage Diff            @@
##            master       #23   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         4           
  Lines           71        87   +16     
  Branches         5         6    +1     
=========================================
+ Hits            71        87   +16     
Impacted Files Coverage Δ
sniffio/__init__.py 100.00% <ø> (ø)
sniffio/_impl.py 100.00% <100.00%> (ø)
sniffio/_tests/test_sniffio.py 100.00% <100.00%> (ø)

@graingert graingert requested a review from njsmith March 9, 2021 00:42
@graingert graingert requested a review from altendky March 9, 2021 10:52
token = current_async_library_cvar.set("my-library's-PyPI-name")
# Your library's step function
def step(...):
old_name, current_async_library_tlocal.name = (
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I decided to expose the ThreadLocal completely without guardrails, otherwise twisted would have to count the stack frames used by any abstraction

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would a context manager be a guardrail that would cause trouble? Or perhaps just a performance difference?

from sniffio import enter_async_library

def step(...):
    with enter_async_library("mylib"):
        result = coro.send(None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you say a bit more about why twisted would have to count stack frames and how that relates to guardrails?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

https://gitter.im/python-trio/general?at=60479824b5131f4f28021225

Kyle Altendorf @altendky 10:45
@graingert:matrix.org since this is related and we're discussing here in gitter: https://github.com/python-trio/sniffio/pull/23#discussion_r590338307 would providing a context manager be problematic?

graingert @graingert:matrix.org [m] 10:46
it's called every step in every coro framework

Kyle Altendorf @altendky 10:46
right, so performance is the concern?

graingert @graingert:matrix.org [m] 10:46
yeah I think so

Probably worth a comment that we are explicitly encouraging not using a context manager.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Can you say a bit more about why twisted would have to count stack frames and how that relates to guardrails?

Twisted counts stack frames for various purposes including determining what frame defer.returnValue was called from

@altendky
Copy link
Copy Markdown
Member

altendky commented Mar 9, 2021

Abbreviating thread to t in the name seems inconsistent with the already_drawn_out_complete_name and my brain thought 'typo' first.

token = current_async_library_cvar.set("my-library's-PyPI-name")
# Your library's step function
def step(...):
old_name, current_async_library_tlocal.name = (
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would a context manager be a guardrail that would cause trouble? Or perhaps just a performance difference?

from sniffio import enter_async_library

def step(...):
    with enter_async_library("mylib"):
        result = coro.send(None)

Comment on lines -89 to -101
There are libraries that can switch back and forth between different
async modes within a single call-task – like ``trio_asyncio`` or
Twisted's asyncio operability. These libraries should make sure to set
the value back and forth at appropriate points.

The general rule of thumb: :data:`current_async_library_cvar` should
be set to X exactly at those moments when ``await X.sleep(...)`` will
work.

.. warning:: You shouldn't attempt to read the value of
``current_async_library_cvar`` directly –
:func:`current_async_library` has a little bit more cleverness than
that.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it somehow now misleading to remind people to think about these things?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hmm, yeah, this text all seems like it's still true, just with the threadlocal instead of the cvar.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think these warnings no-longer apply - you just make the switch before calling throw(), send(), or close() on a coroutine.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

There are libraries that can switch back and forth between different
async modes within a single call-task – like trio_asyncio or
Twisted's asyncio operability.

eg this doesn't apply to twisted because an asyncio.Handle callback can't be called from a generator.send

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't it just a reminder to think about the high level cases while working down in the low level implementation? But sure, there's what, three libraries that implement this... But I guess this discussion would continue in the suggested change below, if at all.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

in the current state of the PR we're advising libraries to read the the value of current_async_library_tlocal directly. And current_async_library_cvar is now undocumented

@graingert graingert changed the title provide sniffio.current_async_library_tlocal.name for coroutine libra… provide sniffio.thread_local.name for coroutine libraries to set Mar 9, 2021
@graingert graingert requested a review from altendky March 9, 2021 21:24
Comment on lines -89 to -101
There are libraries that can switch back and forth between different
async modes within a single call-task – like ``trio_asyncio`` or
Twisted's asyncio operability. These libraries should make sure to set
the value back and forth at appropriate points.

The general rule of thumb: :data:`current_async_library_cvar` should
be set to X exactly at those moments when ``await X.sleep(...)`` will
work.

.. warning:: You shouldn't attempt to read the value of
``current_async_library_cvar`` directly –
:func:`current_async_library` has a little bit more cleverness than
that.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't it just a reminder to think about the high level cases while working down in the low level implementation? But sure, there's what, three libraries that implement this... But I guess this discussion would continue in the suggested change below, if at all.

@graingert graingert requested a review from altendky April 7, 2021 08:22
@altendky
Copy link
Copy Markdown
Member

altendky commented Apr 7, 2021

@graingert, I think my only remaining points were suggesting adding some comments. If you explicitly decline to add them, I'll move on. One bulk refusal here will suffice. :]

Co-authored-by: Kyle Altendorf <sda@fstab.net>
Copy link
Copy Markdown
Member

@altendky altendky left a comment

Choose a reason for hiding this comment

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

I do feel like @njsmith's review is the one that matters, but I don't know of any issues.

@graingert graingert merged commit 949b259 into python-trio:master Apr 9, 2021
@graingert graingert deleted the provide-thread-local branch April 9, 2021 20:08
@graingert
Copy link
Copy Markdown
Member Author

@njsmith because this PR is unreleased my async with as_trio() code is broken because trio uses the contextvar rather than a threadlocal I can't pass a context from the outer-scoped task into the inner-scoped task:

https://gist.github.com/graingert/5356401e196e3671abfa5e33c969a73e#file-demo-py-L138

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