Conversation
384c38b to
e439870
Compare
Python/hamt.c
Outdated
There was a problem hiding this comment.
I think writing URL is time to break 79-columns rule.
Python/hamt.c
Outdated
There was a problem hiding this comment.
Thanks, I completely forgot about it.
Python/hamt.c
Outdated
There was a problem hiding this comment.
No, it's not even exposed to Python.
Python/hamt.c
Outdated
There was a problem hiding this comment.
Python/context.c
Outdated
There was a problem hiding this comment.
dictobject.c makes a big deal about how Python hashes don't need to be randomly allocated. I assume the issue here is that we know these hashes will be used in HAMTs, and compared to regular dicts, HAMTs are more sensitive to equidistribution? Probably it's worth mentioning this motivation in the comment.
Python/hamt.c
Outdated
There was a problem hiding this comment.
Using >> 1 instead of / 2 is 20th century's hack.
All compilers can do this optimization nowdays.
23b9f67 to
62fedcc
Compare
|
@methane Thanks for the initial review, Inada-san. Do you have any other comments/suggestions? |
There was a problem hiding this comment.
GCC produce these errors. I think we have macro for it, but I don't remember it.
Python/hamt.c: In function ‘hamt_node_collision_assoc’:
Python/hamt.c:1432:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^
Python/hamt.c: In function ‘_PyHamt_Without’:
Python/hamt.c:2365:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^
Python/hamt.c: In function ‘hamt_node_bitmap_without’:
Python/hamt.c:1093:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^
Python/hamt.c: In function ‘hamt_node_array_without’:
Python/hamt.c:1911:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^
Python/hamt.c: In function ‘hamt_node_collision_without’:
Python/hamt.c:1526:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^
Python/hamt.c: In function ‘_PyHamt_Find’:
Python/hamt.c:2395:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^
Python/hamt.c: In function ‘hamt_baseiter_tp_iternext’:
Python/hamt.c:2548:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^
Python/hamt.c: In function ‘hamt_py_get’: Python/hamt.c:2801:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^
Python/hamt.c: In function ‘hamt_tp_subscript’:
Python/hamt.c:2749:1: warning: control reaches end of non-void function [-Wreturn-type]
} ^
Include/context.h
Outdated
There was a problem hiding this comment.
Can it be moved to internal header?
There was a problem hiding this comment.
No, because we're using it in _testcapimodule.c to test the HAMT datatype directly in Lib/test/test_context.py.
There was a problem hiding this comment.
(unless I'm missing something and it's possible to expose the HAMT type to Python somehow without making a public C API method)
Python/context.c
Outdated
There was a problem hiding this comment.
I'm not sure what context equality should be.
How is it used?
There was a problem hiding this comment.
It has to be implemented because per PEP 567 Context implements collections.abc.Mapping, which has a __eq__ method.
There was a problem hiding this comment.
And since Context behaves like a Mapping in all its methods, Context.__eq__ should simply compare if another Context object has the same contents or not.
Python/hamt.c
Outdated
There was a problem hiding this comment.
I think we should use val_or_node == val instead of RichCompareBool.
There was a problem hiding this comment.
>>> from _testcapi import hamt
>>> h = hamt()
>>> h2 = h.set('a', [])
>>> h3 = h2.set('a', [])
>>> h3.get('a').append(42)
>>> h2.get('a')
[42]
I tried to reproduce it with contextvars, but I faced fatal error. (Sorry, I used non debug build)
https://gist.github.com/methane/2ce5eda468a4091be60c8233080fe265
There was a problem hiding this comment.
Oh, this is a real bug, thanks so much for finding it!
Funny enough, I initially got this right, and was using val_or_node == val. At sometime I decided to review/document hamt.c and changed it to PyObject_RichCompareBool without giving it too much thought.
Anyways, I'll fix the HAMT and it will also fix the fatal error you saw. That error is interesting on its own. What happened is that PyContextVar_Set caches a borrowed ref to the last value set. Because you were setting another empty list object to the variable, it wasn't really stored (because of the bug in hamt.c), so we stored a borrowed reference to an object that was about to be GCed.
|
|
|
Thank you, Inada-san. |
|
@gvanrossum has just approved the PEP so I guess it's time to merge this! |
|
Is there a pure-Python version of |
|
perhaps I'm missing something, but it seems the fact that only async tasks inherit a copy of their parent contexts is not mentioned in the docs: https://docs.python.org/3/library/contextvars.html It was surprising to me because threading.local did not behave this way by default, and in fact it makes the API behave differently between async and threading worlds per: import asyncio
import contextvars
import threading
var = contextvars.ContextVar('var')
var.set('spam')
def doit():
print(f"thread: {var.get()}")
async def async_sub_doit():
print(f"async sub: {var.get()}")
async def async_doit():
print(f"async: {var.get()}")
var.set('span2')
print(f"async: {var.get()}")
await asyncio.create_task(async_sub_doit())
print(f"main: {var.get()}")
try:
thread = threading.Thread(target=doit)
thread.start()
thread.join()
except Exception as e:
print(f"Thread exception: {e}")
asyncio.run(async_doit())which results in the spawned thread not inheriting the contextvars from the parent, whereas the spawned async tasks do. I think this is an important distinction to make to developers. Personally looking at the API docs I didn't expect it to make a copy given it seems like it's supposed to be a fresh context per task. I think it's neat, but unexpected and certainly looks like magic when looking at code like: https://github.com/DataDog/dd-trace-py/blob/v0.36.1/ddtrace/contrib/asyncio/wrappers.py#L52 which sets the current task's contextvar value, then calls the underlying create task, then swaps back. It seemed to me it wouldn't work because you had to execute under the context of the new task, but thanks to some trickery it ends up working :). TLDR would be nice if documented and clarified behavior between the two modes. |
|
Yes, a task has a copy of contextvars by design. |
The PR implements PEP 567. It consists of a few major blocks:
New public APIs. Files of interest:
Includes/context.h: the public C API;Includes/internal/context.h: structs layout;Python/context.c: implementation;Modules/_contextvarsmodule.candLib/contextvars.py: the contextvars module.Changes in asyncio (including
Modules/_asynciomodule.c).HAMT immutable dict implementation. Files of interest:
Python/hamt.candInclude/internal/hamt.h.HAMT APIs are private, and are not exported to
Python.h.I stress-tested HAMT with millions of random key/value insertions/deletions/modifications. I also used the llvm tooling to ensure that tests cover 99% of the C code. I'm pretty confident that there are no memory leaks and that the implementation is stable.
Few minor changes throughout the codebase, including
pystate.handpystate.c, and modifications of build/make scripts.Tests in
test_contextandtest_asynciopass the refleaks test mode.https://bugs.python.org/issue32436