Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
2865990
Remove deprecated VERSION_TUPLE and bump version to 4.0.0.dev0
ifduyue Jun 20, 2026
4304fa7
Make _xxhash.c lock behaviour controllable via XXHASH_WITH_LOCK and s…
ifduyue Jun 20, 2026
e91b0c6
Build both _xxhash and _xxhash_threadsafe extensions; default is lock…
ifduyue Jun 20, 2026
b92d9ba
Add xxhash.threadsafe submodule backed by the locked _xxhash_threadsa…
ifduyue Jun 20, 2026
38fa45e
Update thread-safety tests to use xxhash.threadsafe
ifduyue Jun 20, 2026
330c42c
Document thread-safety model and the new xxhash.threadsafe submodule
ifduyue Jun 20, 2026
6d0bc68
Unify behaviour across GIL and free-threading builds: default module …
ifduyue Jun 20, 2026
5817b29
Declare both extension variants as Py_MOD_GIL_NOT_USED on Python 3.13+
ifduyue Jun 20, 2026
4e196c9
Use set literal syntax instead of set([...])
ifduyue Jun 20, 2026
6b4c222
Remove empty if (!kwargs) branch in _parse_init_args
ifduyue Jun 20, 2026
d6d3c6e
Fix typo: synchronisation -> synchronization
ifduyue Jun 20, 2026
aaace15
Update XXHASH_DO_UPDATE comment to describe both locked and unlocked …
ifduyue Jun 20, 2026
c1e5b74
Document why build_ext restores self.build_temp in try/finally
ifduyue Jun 20, 2026
155c862
Macro-generate the 4 update() methods via XXHASH_UPDATE_METHOD
ifduyue Jun 20, 2026
83af085
Align trailing backslashes in multi-line macros
ifduyue Jun 20, 2026
e8d9cb2
Remove Py_ALWAYS_INLINE
ifduyue Jun 20, 2026
be06f39
Discourage concurrent update/reset even with xxhash.threadsafe
ifduyue Jun 20, 2026
07e9186
fix(xxhash/__init__.pyi): replace xxh64_* wrong aliases with proper d…
ifduyue Jun 22, 2026
501a1f7
fix(_xxhash.c): always allocate lock at init for Python 3.9-3.12
ifduyue Jun 22, 2026
8639724
refactor: align lock design with CPython 3.15's HASHLIB_EXTERNAL_INST…
ifduyue Jun 22, 2026
54b7d5f
fix: restore try-then-block for PyThread_type_lock to prevent deadloc…
ifduyue Jun 22, 2026
5126e27
fix: distinguish default vs threadsafe types in repr()
ifduyue Jun 22, 2026
bed6ae1
fix: handle PyThread_allocate_lock() failure with MemoryError
ifduyue Jun 22, 2026
4fa9b66
fix: use public module name for tp_name (repr/__module__)
ifduyue Jun 22, 2026
03f4180
test: add threadsafe basic API tests
ifduyue Jun 22, 2026
ec53c68
refactor: group all #define macros together at file top
ifduyue Jun 22, 2026
87a8d4f
chore: reformat setup.py define_macros for readability
ifduyue Jun 22, 2026
9b66e85
test: merge test_name and test_algorithms_available into test_hashlib…
ifduyue Jun 22, 2026
0158265
docs: fix xxh3_64 copy docstrings that incorrectly referred to xxh64
ifduyue Jun 22, 2026
dc9e210
chore: bump version to 4.0.0.dev1
ifduyue Jun 22, 2026
5131776
test: add block_size assertions for xxh3_64/xxh128 and type-distinctn…
ifduyue Jun 23, 2026
ed212aa
style: clean up comments, error messages, and classifiers
ifduyue Jun 23, 2026
24889b4
style: fix misleading #else indentation and update copyright year
ifduyue Jun 23, 2026
e4cfc40
chore(ci): test against Python 3.15.0-beta.2
ifduyue Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
- "3.13t"
- "3.14"
- "3.14t"
- "3.15.0-beta.2"
- "pypy3.9"
- "pypy3.10"
- "pypy3.11"
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ NEXT
~~~~~~~~~~~~~~~~~

- Drop support for Python 3.8
- Remove deprecated ``xxhash.VERSION_TUPLE``
- Default streaming hash objects (``xxh32``, ``xxh64``, ``xxh3_64``,
``xxh3_128``) are no longer thread-safe by default; this removes
per-object locking overhead and restores performance as the primary goal
- Add ``xxhash.threadsafe`` submodule for users who need to share a
streaming hash object across threads; it provides the same API with a
per-object lock
- Both the default module and ``xxhash.threadsafe`` are provided on
free-threading (no-GIL) Python builds, matching the API on regular GIL
builds

v3.7.0 2025-04-25
~~~~~~~~~~~~~~~~~
Expand Down
32 changes: 31 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,36 @@ And aliases:
| xxh128_intdigest = xxh3_128_intdigest
| xxh128_hexdigest = xxh3_128_hexdigest

Thread safety
-------------

The default ``xxhash`` module is optimized for speed. Streaming hash objects
(``xxh32``, ``xxh64``, ``xxh3_64``, ``xxh3_128`` / ``xxh128``) are **not**
thread-safe: do not call ``update()``, ``digest()``, ``copy()``, ``reset()``,
or any other mutating method on the same object from multiple threads without
external synchronization.

One-shot functions (``xxh32_digest``, ``xxh64_hexdigest``, ``xxh3_128_digest``,
etc.) are stateless and always safe to call concurrently.

Concurrent ``update()`` / ``reset()`` on a shared streaming hash object is
discouraged even with locking — prefer one-shot functions or per-thread hash
objects. If you must share a streaming hash across threads, use the
``xxhash.threadsafe`` submodule. It provides the same API with a per-object
lock that serializes all access to the internal xxHash state:

.. code-block:: python

>>> from xxhash import threadsafe
>>> h = threadsafe.xxh64()
>>> # h can be updated from multiple threads, but concurrent update/reset
>>> # still adds overhead and is not recommended

The same two-module split is provided on free-threading (no-GIL) Python
builds: the default module is unlocked, and ``xxhash.threadsafe`` provides a
locked variant.


Caveats
-------

Expand Down Expand Up @@ -282,6 +312,6 @@ functions are required.
Copyright and License
---------------------

Copyright (c) 2014-2025 Yue Du - https://github.com/ifduyue
Copyright (c) 2014-2026 Yue Du - https://github.com/ifduyue

Licensed under `BSD 2-Clause License <http://opensource.org/licenses/BSD-2-Clause>`_
51 changes: 47 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path

from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext as _build_ext

if os.getenv("XXHASH_LINK_SO"):
libraries = ["xxhash"]
Expand All @@ -12,15 +13,55 @@
source = ["src/_xxhash.c", "deps/xxhash/xxhash.c"]
include_dirs = ["deps/xxhash"]

# The default ``xxhash._xxhash`` extension is built without per-object locks
# for maximum performance. Users who need to share a streaming hash object
# across threads can use ``xxhash._xxhash_threadsafe`` (exposed as the public
# ``xxhash.threadsafe`` submodule), which is compiled from the same source
# with locking enabled.

_ext_kwargs = {
"sources": source,
"include_dirs": include_dirs,
"libraries": libraries,
}

ext_modules = [
Extension(
"_xxhash",
source,
include_dirs=include_dirs,
libraries=libraries,
)
**_ext_kwargs,
),
Extension(
"_xxhash_threadsafe",
define_macros=[
("XXHASH_WITH_LOCK", "1"),
("XXHASH_MODULE_NAME", "_xxhash_threadsafe"),
("XXHASH_TP_NAME_PREFIX", "xxhash.threadsafe"),
],
**_ext_kwargs,
),
]


class build_ext(_build_ext):
"""Build each extension in its own temp directory.

Both extensions are built from the same ``src/_xxhash.c`` source file.
Without separate temp directories their object files would overwrite
each other, causing one variant to be linked with the wrong macros.

``try/finally`` restores ``self.build_temp`` so that incremental builds
(where ``build_ext`` may be reused) still work correctly.
"""

def build_extension(self, ext):
old_build_temp = self.build_temp
self.build_temp = os.path.join(old_build_temp, ext.name)
try:
super().build_extension(ext)
finally:
self.build_temp = old_build_temp


d = Path(__file__).parent
long_description = d.joinpath("README.rst").read_text() + "\n" + d.joinpath("CHANGELOG.rst").read_text()

Expand Down Expand Up @@ -53,10 +94,12 @@
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3.15",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Free Threading :: 1 - Unstable",
],
python_requires=">=3.9",
ext_modules=ext_modules,
cmdclass={"build_ext": build_ext},
package_data={"xxhash": ["py.typed", "**.pyi"]},
)
Loading
Loading