10000 gh-135239: simpler use of mutex in hashlib & co by picnixz · Pull Request #135267 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-135239: simpler use of mutex in hashlib & co #135267

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

Merged
merged 45 commits into from
Jun 22, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
5d8c093
add common object head for hashlib/hmac objects
picnixz Jun 8, 2025
81e3046
simplify digest computation
picnixz Jun 8, 2025
7f9f7b7
refactor update logic
picnixz Jun 8, 2025
15a4f2f
refactor alloc() logic
picnixz Jun 8, 2025
5cd828a
finalizing touches
picnixz Jun 8, 2025
63db1de
correct mutex usage
picnixz Jun 15, 2025
ea033a3
Revert 5cd828acdcfef753aee5eec7e13f07682af40f46
picnixz Jun 15, 2025
77baa67
revert some constructor changes
picnixz Jun 15, 2025
902759f
unconditionally lock when performing HASH updates
picnixz Jun 16, 2025
dde68c4
Merge remote-tracking branch 'upstream/main' into perf/hashlib/mutex-…
picnixz Jun 16, 2025
05c1e66
post-merge
picnixz Jun 16, 2025
db57278
do not guard against empty buffers for now
picnixz Jun 16, 2025
ead20a1
consistency fixes
picnixz Jun 16, 2025
68a6bbc
remove unused import
picnixz Jun 16, 2025
68f297e
correct naming for locked/unlocked versions
picnixz Jun 16, 2025
9817c3d
debug?
picnixz Jun 16, 2025
7c6842b
Merge remote-tracking branch 'upstream/main' into perf/hashlib/mutex-…
picnixz Jun 16, 2025
c14c87d
simplify HMAC
picnixz Jun 16, 2025
bfb5436
release the GIL for large buffers
picnixz Jun 16, 2025
923c05f
restore GIL_MINSIZE
picnixz Jun 16, 2025
55b2afa
correctly lock objects
picnixz Jun 16, 2025
5cd60d1
improve tests
picnixz Jun 16, 2025
a2fcbd5
fixup HMAC
picnixz Jun 16, 2025
417cee1
fixup
picnixz Jun 16, 2025
f350501
GIL protection
picnixz Jun 16, 2025
5c4009d
show WASI errors
picnixz Jun 16, 2025
8aec797
fix WASI
picnixz Jun 16, 2025
6db58dc
fix compilation
picnixz Jun 16, 2025
b1f9463
fix compilation
picnixz Jun 16, 2025
491b922
fix warnings
picnixz Jun 16, 2025
c048975
sync
picnixz Jun 17, 2025
c9044d2
fixup format string
picnixz Jun 17, 2025
6c08f0d
address review
picnixz Jun 17, 2025
7fd1396
reudce diff
picnixz Jun 20, 2025
f400a11
Merge remote-tracking branch 'upstream/main' into perf/hashlib/mutex-…
picnixz Jun 20, 2025
5e2daa8
Merge remote-tracking branch 'upstream/main' into perf/hashlib/mutex-…
picnixz Jun 20, 2025
4f9729e
Merge remote-tracking branch 'upstream/main' into perf/hashlib/mutex-…
picnixz Jun 20, 2025
06aaee0
Merge branch 'main' into perf/hashlib/mutex-135239
picnixz Jun 21, 2025
977c807
fixup
picnixz Jun 21, 2025
6d66fef
fixup
picnixz Jun 21, 2025
c9db0b1
make the test suite less slow
picnixz Jun 21, 2025
6ffdd1c
fix test when GIL_MINSIZE is changed
picnixz Jun 21, 2025
98ec915
defer cosmetics
picnixz Jun 21, 2025
398ddb3
Update Lib/test/test_hashlib.py
picnixz Jun 21, 2025
0ae70e9
improve test
picnixz Jun 22, 2025
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
Prev Previous commit
Next Next commit
correctly lock objects
  • Loading branch information
picnixz committed Jun 16, 2025
commit 55b2afabcd516207125c4774111a5c4288b5c890
10 changes: 8 additions & 2 deletions Lib/test/test_hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1027,17 +1027,23 @@ def test_sha256_gil(self):
@threading_helper.reap_threads
@threading_helper.requires_working_threading()
def test_threaded_hashing(self):
for constructor in self.hash_constructors:
if constructor().name not in self.shakes:
with self.subTest(constructor=constructor):
self.do_test_threaded_hashing(constructor)

def do_test_threaded_hashing(self, constructor):
# Updating the same hash object from several threads at once
# using data chunk sizes containing the same byte sequences.
#
# If the internal locks are working to prevent multiple
# updates on the same object from running at once, the resulting
# hash will be the same as doing it single threaded upfront.
hasher = hashlib.sha1()
hasher = constructor()
num_threads = 5
smallest_data = b'swineflu'
data = smallest_data * 200000
expected_hash = hashlib.sha1(data*num_threads).hexdigest()
expected_hash = constructor(data*num_threads).hexdigest()

def hash_in_chunks(chunk_size):
index = 0
Expand Down
39 changes: 16 additions & 23 deletions Modules/_hashopenssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -278,21 +278,15 @@ get_hashlib_state(PyObject *module)
}

typedef struct {
PyObject_HEAD
PyObject_HASHLIB_HEAD
EVP_MD_CTX *ctx; /* OpenSSL message digest context */
// Prevents undefined behavior via multiple threads entering the C API.
bool use_mutex;
PyMutex mutex; /* OpenSSL context lock */
} HASHobject;

#define HASHobject_CAST(op) ((HASHobject *)(op))

typedef struct {
PyObject_HEAD
PyObject_HASHLIB_HEAD
HMAC_CTX *ctx; /* OpenSSL hmac context */
// Prevents undefined behavior via multiple threads entering the C API.
bool use_mutex;
PyMutex mutex; /* HMAC context lock */
} HMACobject;

#define HMACobject_CAST(op) ((HMACobject *)(op))
Expand Down Expand Up @@ -803,11 +797,10 @@ _hashlib_HASH_update_impl(HASHobject *self, PyObject *obj)
int result;
Py_buffer view;
GET_BUFFER_VIEW_OR_ERROUT(obj, &view);
Py_BEGIN_ALLOW_THREADS
HASHLIB_ACQUIRE_LOCK(self);
result = _hashlib_HASH_hash(self, view.buf, view.len);
HASHLIB_RELEASE_LOCK(self);
Py_END_ALLOW_THREADS
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
self, HASHLIB_GIL_MINSIZE,
result = 10000 _hashlib_HASH_hash(self, view.buf, view.len)
);
PyBuffer_Release(&view);
return result < 0 ? NULL : Py_None;
}
Expand Down Expand Up @@ -1114,9 +1107,10 @@ _hashlib_HASH(PyObject *module, const char *digestname, PyObject *data_obj,
if (view.buf && view.len) {
/* Do not use self->mutex here as this is the constructor
* where it is not yet possible to have concurrent access. */
Py_BEGIN_ALLOW_THREADS
result = _hashlib_HASH_hash(self, view.buf, view.len);
Py_END_ALLOW_THREADS
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
view.len,
result = _hashlib_HASH_hash(self, view.buf, view.len)
);
if (result == -1) {
assert(PyErr_Occurred());
Py_CLEAR(self);
Expand Down Expand Up @@ -1810,13 +1804,12 @@ _hmac_update(HMACobject *self, PyObject *obj)
Py_buffer view = {0};

GET_BUFFER_VIEW_OR_ERROR(obj, &view, return 0);
Py_BEGIN_ALLOW_THREADS
HASHLIB_ACQUIRE_LOCK(self);
r = HMAC_Update(self->ctx,
(const unsigned char *)view.buf,
(size_t)view.len);
HASHLIB_RELEASE_LOCK(self);
Py_END_ALLOW_THREADS
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
self, view.len,
r = HMAC_Update(
self->ctx, (const unsigned char *)view.buf, (size_t)view.len
)
);
PyBuffer_Release(&view);

if (r == 0) {
Expand Down
8 changes: 4 additions & 4 deletions Modules/blake2module.c
Original file line number Diff line number Diff line change
Expand Up @@ -646,10 +646,10 @@ py_blake2_new(PyTypeObject *type, PyObject *data, int digest_size,
GET_BUFFER_VIEW_OR_ERROR(data, &buf, goto error);
/* Do not use self->mutex here as this is the constructor
* where it is not yet possible to have concurrent access. */
HASHLIB_EXTERNAL_INSTRUCTIONS(
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
buf.len,
blake2_update_unlocked(self, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
}

Expand Down Expand Up @@ -822,10 +822,10 @@ _blake2_blake2b_update_impl(Blake2Object *self, PyObject *data)
{
Py_buffer buf;
GET_BUFFER_VIEW_OR_ERROUT(data, &buf);
HASHLIB_EXTERNAL_INSTRUCTIONS_WITH_MUTEX(
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
self, buf.len,
blake2_update_unlocked(self, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
Expand Down
46 changes: 26 additions & 20 deletions Modules/hashlib.h
B41A
Original file line number Diff line number Diff line change
Expand Up @@ -64,27 +64,33 @@
} while (0)

#define HASHLIB_GIL_MINSIZE 2048
#define HASHLIB_EXTERNAL_INSTRUCTIONS(SIZE, STATEMENTS) \
if ((SIZE) > HASHLIB_GIL_MINSIZE) { \
Py_BEGIN_ALLOW_THREADS \
STATEMENTS; \
Py_END_ALLOW_THREADS \
} \
else { \
STATEMENTS; \
}
#define HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(SIZE, STATEMENTS) \
do { \
if ((SIZE) > HASHLIB_GIL_MINSIZE) { \
Py_BEGIN_ALLOW_THREADS \
STATEMENTS; \
Py_END_ALLOW_THREADS \
} \
else { \
STATEMENTS; \
} \
} while (0)

#define HASHLIB_EXTERNAL_INSTRUCTIONS_WITH_MUTEX(OBJ, SIZE, STATEMENTS) \
if ((SIZE) > HASHLIB_GIL_MINSIZE) { \
Py_BEGIN_ALLOW_THREADS \
HASHLIB_ACQUIRE_LOCK(OBJ); \
STATEMENTS; \
HASHLIB_RELEASE_LOCK(OBJ); \
Py_END_ALLOW_THREADS \
} \
else { \
STATEMENTS; \
}
#define HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(OBJ, SIZE, STATEMENTS) \
do { \
if ((SIZE) > HASHLIB_GIL_MINSIZE) { \
Py_BEGIN_ALLOW_THREADS \
HASHLIB_ACQUIRE_LOCK(OBJ); \
STATEMENTS; \
HASHLIB_RELEASE_LOCK(OBJ); \
Py_END_ALLOW_THREADS \
} \
else { \
HASHLIB_ACQUIRE_LOCK(OBJ); \
STATEMENTS; \
HASHLIB_RELEASE_LOCK(OBJ); \
} \
} while (0)

static inline int
_Py_hashlib_data_argument(PyObject **res, PyObject *data, PyObject *string)
Expand Down
6 changes: 3 additions & 3 deletions Modules/hmacmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ _hmac_new_impl(PyObject *module, PyObject *keyobj, PyObject *msgobj,
GET_BUFFER_VIEW_OR_ERROR(msgobj, &msg, goto error);
/* Do not use self->mutex here as this is the constructor
* where it is not yet possible to have concurrent access. */
HASHLIB_EXTERNAL_INSTRUCTIONS(
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
msg.len,
_hacl_hmac_state_update(self->state, msg.buf, msg.len)
);
Expand Down Expand Up @@ -888,10 +888,10 @@ _hmac_HMAC_update_impl(HMACObject *self, PyObject *msgobj)
int rc = 0;
Py_buffer msg;
GET_BUFFER_VIEW_OR_ERROUT(msgobj, &msg);
HASHLIB_EXTERNAL_INSTRUCTIONS_WITH_MUTEX(
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
self, msg.len,
rc = _hacl_hmac_state_update(self->state, msg.buf, msg.len)
)
);
PyBuffer_Release(&msg);
return rc < 0 ? NULL : Py_None;
}
Expand Down
8 changes: 4 additions & 4 deletions Modules/md5module.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ MD5Type_update_impl(MD5object *self, PyObject *obj)
{
Py_buffer buf;
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
HASHLIB_EXTERNAL_INSTRUCTIONS_WITH_MUTEX(
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
self, buf.len,
_hacl_md5_state_update(self->hash_state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
Expand Down Expand Up @@ -300,10 +300,10 @@ _md5_md5_impl(PyObject *module, PyObject *data, int usedforsecurity,
if (string) {
/* Do not use self->mutex here as this is the constructor
* where it is not yet possible to have concurrent access. */
HASHLIB_EXTERNAL_INSTRUCTIONS(
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
buf.len,
_hacl_md5_state_update(new->hash_state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
}

Expand Down
8 changes: 4 additions & 4 deletions Modules/sha1module.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,10 @@ SHA1Type_update_impl(SHA1object *self, PyObject *obj)
{
Py_buffer buf;
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
HASHLIB_EXTERNAL_INSTRUCTIONS_WITH_MUTEX(
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
self, buf.len,
_hacl_sha1_state_update(self->hash_state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
Expand Down Expand Up @@ -302,10 +302,10 @@ _sha1_sha1_impl(PyObject *module, PyObject *data, int usedforsecurity,
if (string) {
/* Do not use self->mutex here as this is the constructor
* where it is not yet possible to have concurrent access. */
HASHLIB_EXTERNAL_INSTRUCTIONS(
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
buf.len,
_hacl_sha1_state_update(new->hash_state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
}

Expand Down
24 changes: 12 additions & 12 deletions Modules/sha2module.c
Original file line number Diff line number Diff line change
Expand Up @@ -406,10 +406,10 @@ SHA256Type_update_impl(SHA256object *self, PyObject *obj)
{
Py_buffer buf;
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
HASHLIB_EXTERNAL_INSTRUCTIONS_WITH_MUTEX(
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
self, buf.len,
_hacl_sha2_state_update_256(self->state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
Expand All @@ -429,10 +429,10 @@ SHA512Type_update_impl(SHA512object *self, PyObject *obj)
{
Py_buffer buf;
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
HASHLIB_EXTERNAL_INSTRUCTIONS_WITH_MUTEX(
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
self, buf.len,
_hacl_sha2_state_update_512(self->state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
Expand Down Expand Up @@ -614,10 +614,10 @@ _sha2_sha256_impl(PyObject *module, PyObject *data, int usedforsecurity,
if (string) {
/* Do not use self->mutex here as this is the constructor
* where it is not yet possible to have concurrent access. */
HASHLIB_EXTERNAL_INSTRUCTIONS(
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
buf.len,
_hacl_sha2_state_update_256(new->state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
}

Expand Down Expand Up @@ -672,10 +672,10 @@ _sha2_sha224_impl(PyObject *module, PyObject *data, int usedforsecurity,
if (string) {
/* Do not use self->mutex here as this is the constructor
* where it is not yet possible to have concurrent access. */
HASHLIB_EXTERNAL_INSTRUCTIONS(
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
buf.len,
_hacl_sha2_state_update_256(new->state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
}

Expand Down Expand Up @@ -731,10 +731,10 @@ _sha2_sha512_impl(PyObject *module, PyObject *data, int usedforsecurity,
if (string) {
/* Do not use self->mutex here as this is the constructor
* where it is not yet possible to have concurrent access. */
HASHLIB_EXTERNAL_INSTRUCTIONS(
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
buf.len,
_hacl_sha2_state_update_512(new->state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
}

Expand Down Expand Up @@ -790,10 +790,10 @@ _sha2_sha384_impl(PyObject *module, PyObject *data, int usedforsecurity,
if (string) {
/* Do not use self->mutex here as this is the constructor
* where it is not yet possible to have concurrent access. */
HASHLIB_EXTERNAL_INSTRUCTIONS(
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
buf.len,
_hacl_sha2_state_update_512(new->state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
}

Expand Down
8 changes: 4 additions & 4 deletions Modules/sha3module.c
< 2851 /div>
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,10 @@ py_sha3_new_impl(PyTypeObject *type, PyObject *data_obj, int usedforsecurity,
GET_BUFFER_VIEW_OR_ERROR(data, &buf, goto error);
/* Do not use self->mutex here as this is the constructor
* where it is not yet possible to have concurrent access. */
HASHLIB_EXTERNAL_INSTRUCTIONS(
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
buf.len,
_hacl_sha3_state_update(self->hash_state, buf.buf, buf.len)
)
);
}

PyBuffer_Release(&buf);
Expand Down Expand Up @@ -298,10 +298,10 @@ _sha3_sha3_224_update_impl(SHA3object *self, PyObject *data)
{
Py_buffer buf;
GET_BUFFER_VIEW_OR_ERROUT(data, &buf);
HASHLIB_EXTERNAL_INSTRUCTIONS_WITH_MUTEX(
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
self, buf.len,
_hacl_sha3_state_update(self->hash_state, buf.buf, buf.len)
)
);
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}
Expand Down
Loading
0