Skip to content

Commit 6fe7c76

Browse files
authored
Merge branch 'main' into support-IPv6-in-cookiejar
2 parents 36cb03b + e7295a8 commit 6fe7c76

13 files changed

Lines changed: 424 additions & 627 deletions

File tree

Doc/c-api/extension-modules.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,6 @@ in the following ways:
242242
* Single-phase modules support module lookup functions like
243243
:c:func:`PyState_FindModule`.
244244

245-
.. [#testsinglephase] ``_testsinglephase`` is an internal module used \
246-
in CPython's self-test suite; your installation may or may not \
245+
.. [#testsinglephase] ``_testsinglephase`` is an internal module used
246+
in CPython's self-test suite; your installation may or may not
247247
include it.

Lib/test/libregrtest/tsan.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
'test_capi.test_pyatomic',
99
'test_code',
1010
'test_ctypes',
11-
# 'test_concurrent_futures', # gh-130605: too many data races
11+
'test_concurrent_futures',
1212
'test_enum',
1313
'test_functools',
1414
'test_httpservers',

Lib/test/test_hashlib.py

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,57 +1043,78 @@ def test_gil(self):
10431043

10441044
def test_sha256_gil(self):
10451045
gil_minsize = hashlib_helper.find_gil_minsize(['_sha2', '_hashlib'])
1046+
data = b'1' + b'#' * gil_minsize + b'1'
1047+
expected = hashlib.sha256(data).hexdigest()
1048+
10461049
m = hashlib.sha256()
10471050
m.update(b'1')
10481051
m.update(b'#' * gil_minsize)
10491052
m.update(b'1')
1050-
self.assertEqual(
1051-
m.hexdigest(),
1052-
'1cfceca95989f51f658e3f3ffe7f1cd43726c9e088c13ee10b46f57cef135b94'
1053-
)
1053+
self.assertEqual(m.hexdigest(), expected)
10541054

1055-
m = hashlib.sha256(b'1' + b'#' * gil_minsize + b'1')
1056-
self.assertEqual(
1057-
m.hexdigest(),
1058-
'1cfceca95989f51f658e3f3ffe7f1cd43726c9e088c13ee10b46f57cef135b94'
1059-
)
1055+
@threading_helper.reap_threads
1056+
@threading_helper.requires_working_threading()
1057+
def test_threaded_hashing_fast(self):
1058+
# Same as test_threaded_hashing_slow() but only tests some functions
1059+
# since otherwise test_hashlib.py becomes too slow during development.
1060+
for name in ['md5', 'sha1', 'sha256', 'sha3_256', 'blake2s']:
1061+
if constructor := getattr(hashlib, name, None):
1062+
with self.subTest(name):
1063+
self.do_test_threaded_hashing(constructor, is_shake=False)
1064+
if shake_128 := getattr(hashlib, 'shake_128', None):
1065+
self.do_test_threaded_hashing(shake_128, is_shake=True)
10601066

1067+
@requires_resource('cpu')
10611068
@threading_helper.reap_threads
10621069
@threading_helper.requires_working_threading()
1063-
def test_threaded_hashing(self):
1070+
def test_threaded_hashing_slow(self):
1071+
for algorithm, constructors in self.constructors_to_test.items():
1072+
is_shake = algorithm in self.shakes
1073+
for constructor in constructors:
1074+
with self.subTest(constructor.__name__, is_shake=is_shake):
1075+
self.do_test_threaded_hashing(constructor, is_shake)
1076+
1077+
def do_test_threaded_hashing(self, constructor, is_shake):
10641078
# Updating the same hash object from several threads at once
10651079
# using data chunk sizes containing the same byte sequences.
10661080
#
10671081
# If the internal locks are working to prevent multiple
10681082
# updates on the same object from running at once, the resulting
10691083
# hash will be the same as doing it single threaded upfront.
1070-
hasher = hashlib.sha1()
1071-
num_threads = 5
1072-
smallest_data = b'swineflu'
1073-
data = smallest_data * 200000
1074-
expected_hash = hashlib.sha1(data*num_threads).hexdigest()
1075-
1076-
def hash_in_chunks(chunk_size):
1077-
index = 0
1078-
while index < len(data):
1079-
hasher.update(data[index:index + chunk_size])
1080-
index += chunk_size
1084+
1085+
# The data to hash has length s|M|q^N and the chunk size for the i-th
1086+
# thread is s|M|q^(N-i), where N is the number of threads, M is a fixed
1087+
# message of small length, and s >= 1 and q >= 2 are small integers.
1088+
smallest_size, num_threads, s, q = 8, 5, 2, 10
1089+
1090+
smallest_data = os.urandom(smallest_size)
1091+
data = s * smallest_data * (q ** num_threads)
1092+
1093+
h1 = constructor(usedforsecurity=False)
1094+
h2 = constructor(data * num_threads, usedforsecurity=False)
1095+
1096+
def update(chunk_size):
1097+
for index in range(0, len(data), chunk_size):
1098+
h1.update(data[index:index + chunk_size])
10811099

10821100
threads = []
1083-
for threadnum in range(num_threads):
1084-
chunk_size = len(data) // (10 ** threadnum)
1101+
for thread_num in range(num_threads):
1102+
# chunk_size = len(data) // (q ** thread_num)
1103+
chunk_size = s * smallest_size * q ** (num_threads - thread_num)
10851104
self.assertGreater(chunk_size, 0)
1086-
self.assertEqual(chunk_size % len(smallest_data), 0)
1087-
thread = threading.Thread(target=hash_in_chunks,
1088-
args=(chunk_size,))
1105+
self.assertEqual(chunk_size % smallest_size, 0)
1106+
thread = threading.Thread(target=update, args=(chunk_size,))
10891107
threads.append(thread)
10901108

10911109
for thread in threads:
10921110
thread.start()
10931111
for thread in threads:
10941112
thread.join()
10951113

1096-
self.assertEqual(expected_hash, hasher.hexdigest())
1114+
if is_shake:
1115+
self.assertEqual(h1.hexdigest(16), h2.hexdigest(16))
1116+
else:
1117+
self.assertEqual(h1.hexdigest(), h2.hexdigest())
10971118

10981119
def test_get_fips_mode(self):
10991120
fips_mode = self.is_fips_mode

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,7 @@ Anton Kasyanov
943943
Lou Kates
944944
Makoto Kato
945945
Irit Katriel
946+
Kattni
946947
Hiroaki Kawai
947948
Dmitry Kazakov
948949
Brian Kearns

Modules/_hashopenssl.c

Lines changed: 31 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -278,21 +278,15 @@ get_hashlib_state(PyObject *module)
278278
}
279279

280280
typedef struct {
281-
PyObject_HEAD
281+
HASHLIB_OBJECT_HEAD
282282
EVP_MD_CTX *ctx; /* OpenSSL message digest context */
283-
// Prevents undefined behavior via multiple threads entering the C API.
284-
bool use_mutex;
285-
PyMutex mutex; /* OpenSSL context lock */
286283
} HASHobject;
287284

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

290287
typedef struct {
291-
PyObject_HEAD
288+
HASHLIB_OBJECT_HEAD
292289
HMAC_CTX *ctx; /* OpenSSL hmac context */
293-
// Prevents undefined behavior via multiple threads entering the C API.
294-
bool use_mutex;
295-
PyMutex mutex; /* HMAC context lock */
296290
} HMACobject;
297291

298292
#define HMACobject_CAST(op) ((HMACobject *)(op))
@@ -700,9 +694,9 @@ static int
700694
_hashlib_HASH_copy_locked(HASHobject *self, EVP_MD_CTX *new_ctx_p)
701695
{
702696
int result;
703-
ENTER_HASHLIB(self);
697+
HASHLIB_ACQUIRE_LOCK(self);
704698
result = EVP_MD_CTX_copy(new_ctx_p, self->ctx);
705-
LEAVE_HASHLIB(self);
699+
HASHLIB_RELEASE_LOCK(self);
706700
if (result == 0) {
707701
notify_smart_ssl_error_occurred_in(Py_STRINGIFY(EVP_MD_CTX_copy));
708702
return -1;
@@ -802,27 +796,13 @@ _hashlib_HASH_update_impl(HASHobject *self, PyObject *obj)
802796
{
803797
int result;
804798
Py_buffer view;
805-
806799
GET_BUFFER_VIEW_OR_ERROUT(obj, &view);
807-
808-
if (!self->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) {
809-
self->use_mutex = true;
810-
}
811-
if (self->use_mutex) {
812-
Py_BEGIN_ALLOW_THREADS
813-
PyMutex_Lock(&self->mutex);
814-
result = _hashlib_HASH_hash(self, view.buf, view.len);
815-
PyMutex_Unlock(&self->mutex);
816-
Py_END_ALLOW_THREADS
817-
} else {
818-
result = _hashlib_HASH_hash(self, view.buf, view.len);
819-
}
820-
800+
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
801+
self, view.len,
802+
result = _hashlib_HASH_hash(self, view.buf, view.len)
803+
);
821804
PyBuffer_Release(&view);
822-
823-
if (result == -1)
824-
return NULL;
825-
Py_RETURN_NONE;
805+
return result < 0 ? NULL : Py_None;
826806
}
827807

828808
static PyMethodDef HASH_methods[] = {
@@ -945,6 +925,10 @@ _hashlib_HASHXOF_digest_impl(HASHobject *self, Py_ssize_t length)
945925
return NULL;
946926
}
947927

928+
if (length == 0) {
929+
return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
930+
}
931+
948932
retval = PyBytes_FromStringAndSize(NULL, length);
949933
if (retval == NULL) {
950934
return NULL;
@@ -997,6 +981,10 @@ _hashlib_HASHXOF_hexdigest_impl(HASHobject *self, Py_ssize_t length)
997981
return NULL;
998982
}
999983

984+
if (length == 0) {
985+
return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
986+
}
987+
1000988
digest = (unsigned char*)PyMem_Malloc(length);
1001989
if (digest == NULL) {
1002990
(void)PyErr_NoMemory();
@@ -1136,15 +1124,12 @@ _hashlib_HASH(PyObject *module, const char *digestname, PyObject *data_obj,
11361124
}
11371125

11381126
if (view.buf && view.len) {
1139-
if (view.len >= HASHLIB_GIL_MINSIZE) {
1140-
/* We do not initialize self->lock here as this is the constructor
1141-
* where it is not yet possible to have concurrent access. */
1142-
Py_BEGIN_ALLOW_THREADS
1143-
result = _hashlib_HASH_hash(self, view.buf, view.len);
1144-
Py_END_ALLOW_THREADS
1145-
} else {
1146-
result = _hashlib_HASH_hash(self, view.buf, view.len);
1147-
}
1127+
/* Do not use self->mutex here as this is the constructor
1128+
* where it is not yet possible to have concurrent access. */
1129+
HASHLIB_EXTERNAL_INSTRUCTIONS_UNLOCKED(
1130+
view.len,
1131+
result = _hashlib_HASH_hash(self, view.buf, view.len)
1132+
);
11481133
if (result == -1) {
11491134
assert(PyErr_Occurred());
11501135
Py_CLEAR(self);
@@ -1805,9 +1790,9 @@ static int
18051790
locked_HMAC_CTX_copy(HMAC_CTX *new_ctx_p, HMACobject *self)
18061791
{
18071792
int result;
1808-
ENTER_HASHLIB(self);
1793+
HASHLIB_ACQUIRE_LOCK(self);
18091794
result = HMAC_CTX_copy(new_ctx_p, self->ctx);
1810-
LEAVE_HASHLIB(self);
1795+
HASHLIB_RELEASE_LOCK(self);
18111796
if (result == 0) {
18121797
notify_smart_ssl_error_occurred_in(Py_STRINGIFY(HMAC_CTX_copy));
18131798
return -1;
@@ -1838,24 +1823,12 @@ _hmac_update(HMACobject *self, PyObject *obj)
18381823
Py_buffer view = {0};
18391824

18401825
GET_BUFFER_VIEW_OR_ERROR(obj, &view, return 0);
1841-
1842-
if (!self->use_mutex && view.len >= HASHLIB_GIL_MINSIZE) {
1843-
self->use_mutex = true;
1844-
}
1845-
if (self->use_mutex) {
1846-
Py_BEGIN_ALLOW_THREADS
1847-
PyMutex_Lock(&self->mutex);
1848-
r = HMAC_Update(self->ctx,
1849-
(const unsigned char *)view.buf,
1850-
(size_t)view.len);
1851-
PyMutex_Unlock(&self->mutex);
1852-
Py_END_ALLOW_THREADS
1853-
} else {
1854-
r = HMAC_Update(self->ctx,
1855-
(const unsigned char *)view.buf,
1856-
(size_t)view.len);
1857-
}
1858-
1826+
HASHLIB_EXTERNAL_INSTRUCTIONS_LOCKED(
1827+
self, view.len,
1828+
r = HMAC_Update(
1829+
self->ctx, (const unsigned char *)view.buf, (size_t)view.len
1830+
)
1831+
);
18591832
PyBuffer_Release(&view);
18601833

18611834
if (r == 0) {

0 commit comments

Comments
 (0)