--- /dev/null
+We now release the GIL around built-in :mod:`hashlib` computations of
+reasonable size for the SHA families and MD5 hash functions, matching
+what our OpenSSL backed hash computations already does.
typedef struct {
PyObject_HEAD
EVP_MD_CTX *ctx; /* OpenSSL message digest context */
+ // Prevents undefined behavior via multiple threads entering the C API.
+ // The lock will be NULL before threaded access has been enabled.
PyThread_type_lock lock; /* OpenSSL context lock */
} EVPobject;
typedef struct {
PyObject_HEAD
HMAC_CTX *ctx; /* OpenSSL hmac context */
+ // Prevents undefined behavior via multiple threads entering the C API.
+ // The lock will be NULL before threaded access has been enabled.
PyThread_type_lock lock; /* HMAC context lock */
} HMACobject;
if (view.buf && view.len) {
if (view.len >= HASHLIB_GIL_MINSIZE) {
+ /* We do not initialize self->lock here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
Py_BEGIN_ALLOW_THREADS
result = EVP_hash(self, view.buf, view.len);
Py_END_ALLOW_THREADS
* LEAVE_HASHLIB block or explicitly acquire and release the lock inside
* a PY_BEGIN / END_ALLOW_THREADS block if they wish to release the GIL for
* an operation.
+ *
+ * These only drop the GIL if the lock acquisition itself is likely to
+ * block. Thus the non-blocking acquire gating the GIL release for a
+ * blocking lock acquisition. The intent of these macros is to surround
+ * the assumed always "fast" operations that you aren't releasing the
+ * GIL around. Otherwise use code similar to what you see in hash
+ * function update() methods.
*/
#include "pythread.h"
PyThread_release_lock((obj)->lock); \
}
-/* TODO(gps): We should probably make this a module or EVPobject attribute
+/* TODO(gpshead): We should make this a module or class attribute
* to allow the user to optimize based on the platform they're using. */
#define HASHLIB_GIL_MINSIZE 2048
typedef struct {
PyObject_HEAD
-
+ // Prevents undefined behavior via multiple threads entering the C API.
+ // The lock will be NULL before threaded access has been enabled.
+ PyThread_type_lock lock;
Hacl_Streaming_MD5_state *hash_state;
} MD5object;
newMD5object(MD5State * st)
{
MD5object *md5 = (MD5object *)PyObject_GC_New(MD5object, st->md5_type);
+ md5->lock = NULL;
PyObject_GC_Track(md5);
return md5;
}
MD5_dealloc(MD5object *ptr)
{
Hacl_Streaming_MD5_legacy_free(ptr->hash_state);
+ if (ptr->lock != NULL) {
+ PyThread_free_lock(ptr->lock);
+ }
PyTypeObject *tp = Py_TYPE(ptr);
PyObject_GC_UnTrack(ptr);
PyObject_GC_Del(ptr);
if ((newobj = newMD5object(st))==NULL)
return NULL;
+ ENTER_HASHLIB(self);
newobj->hash_state = Hacl_Streaming_MD5_legacy_copy(self->hash_state);
+ LEAVE_HASHLIB(self);
return (PyObject *)newobj;
}
/*[clinic end generated code: output=eb691dc4190a07ec input=bc0c4397c2994be6]*/
{
unsigned char digest[MD5_DIGESTSIZE];
+ ENTER_HASHLIB(self);
Hacl_Streaming_MD5_legacy_finish(self->hash_state, digest);
+ LEAVE_HASHLIB(self);
return PyBytes_FromStringAndSize((const char *)digest, MD5_DIGESTSIZE);
}
/*[clinic end generated code: output=17badced1f3ac932 input=b60b19de644798dd]*/
{
unsigned char digest[MD5_DIGESTSIZE];
+ ENTER_HASHLIB(self);
Hacl_Streaming_MD5_legacy_finish(self->hash_state, digest);
+ LEAVE_HASHLIB(self);
return _Py_strhex((const char*)digest, MD5_DIGESTSIZE);
}
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
- update(self->hash_state, buf.buf, buf.len);
+ if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->lock = PyThread_allocate_lock();
+ }
+ if (self->lock != NULL) {
+ Py_BEGIN_ALLOW_THREADS
+ PyThread_acquire_lock(self->lock, 1);
+ update(self->hash_state, buf.buf, buf.len);
+ PyThread_release_lock(self->lock);
+ Py_END_ALLOW_THREADS
+ } else {
+ update(self->hash_state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
Py_RETURN_NONE;
return NULL;
}
if (string) {
- update(new->hash_state, buf.buf, buf.len);
+ if (buf.len >= HASHLIB_GIL_MINSIZE) {
+ /* We do not initialize self->lock here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ Py_BEGIN_ALLOW_THREADS
+ update(new->hash_state, buf.buf, buf.len);
+ Py_END_ALLOW_THREADS
+ } else {
+ update(new->hash_state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
}
typedef struct {
PyObject_HEAD
-
+ // Prevents undefined behavior via multiple threads entering the C API.
+ // The lock will be NULL before threaded access has been enabled.
+ PyThread_type_lock lock;
Hacl_Streaming_SHA1_state *hash_state;
} SHA1object;
newSHA1object(SHA1State *st)
{
SHA1object *sha = (SHA1object *)PyObject_GC_New(SHA1object, st->sha1_type);
+ sha->lock = NULL;
PyObject_GC_Track(sha);
return sha;
}
SHA1_dealloc(SHA1object *ptr)
{
Hacl_Streaming_SHA1_legacy_free(ptr->hash_state);
+ if (ptr->lock != NULL) {
+ PyThread_free_lock(ptr->lock);
+ }
PyTypeObject *tp = Py_TYPE(ptr);
PyObject_GC_UnTrack(ptr);
PyObject_GC_Del(ptr);
if ((newobj = newSHA1object(st)) == NULL)
return NULL;
+ ENTER_HASHLIB(self);
newobj->hash_state = Hacl_Streaming_SHA1_legacy_copy(self->hash_state);
+ LEAVE_HASHLIB(self);
return (PyObject *)newobj;
}
/*[clinic end generated code: output=2f05302a7aa2b5cb input=13824b35407444bd]*/
{
unsigned char digest[SHA1_DIGESTSIZE];
+ ENTER_HASHLIB(self);
Hacl_Streaming_SHA1_legacy_finish(self->hash_state, digest);
+ LEAVE_HASHLIB(self);
return PyBytes_FromStringAndSize((const char *)digest, SHA1_DIGESTSIZE);
}
/*[clinic end generated code: output=4161fd71e68c6659 input=97691055c0c74ab0]*/
{
unsigned char digest[SHA1_DIGESTSIZE];
+ ENTER_HASHLIB(self);
Hacl_Streaming_SHA1_legacy_finish(self->hash_state, digest);
+ LEAVE_HASHLIB(self);
return _Py_strhex((const char *)digest, SHA1_DIGESTSIZE);
}
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
- update(self->hash_state, buf.buf, buf.len);
+ if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->lock = PyThread_allocate_lock();
+ }
+ if (self->lock != NULL) {
+ Py_BEGIN_ALLOW_THREADS
+ PyThread_acquire_lock(self->lock, 1);
+ update(self->hash_state, buf.buf, buf.len);
+ PyThread_release_lock(self->lock);
+ Py_END_ALLOW_THREADS
+ } else {
+ update(self->hash_state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
Py_RETURN_NONE;
return NULL;
}
if (string) {
- update(new->hash_state, buf.buf, buf.len);
+ if (buf.len >= HASHLIB_GIL_MINSIZE) {
+ /* We do not initialize self->lock here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ Py_BEGIN_ALLOW_THREADS
+ update(new->hash_state, buf.buf, buf.len);
+ Py_END_ALLOW_THREADS
+ } else {
+ update(new->hash_state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
}
typedef struct {
PyObject_HEAD
int digestsize;
+ // Prevents undefined behavior via multiple threads entering the C API.
+ // The lock will be NULL before threaded access has been enabled.
+ PyThread_type_lock lock;
Hacl_Streaming_SHA2_state_sha2_256 *state;
} SHA256object;
typedef struct {
PyObject_HEAD
int digestsize;
+ // Prevents undefined behavior via multiple threads entering the C API.
+ // The lock will be NULL before threaded access has been enabled.
+ PyThread_type_lock lock;
Hacl_Streaming_SHA2_state_sha2_512 *state;
} SHA512object;
if (!sha) {
return NULL;
}
+ sha->lock = NULL;
PyObject_GC_Track(sha);
return sha;
}
if (!sha) {
return NULL;
}
+ sha->lock = NULL;
PyObject_GC_Track(sha);
return sha;
}
if (!sha) {
return NULL;
}
+ sha->lock = NULL;
PyObject_GC_Track(sha);
return sha;
}
if (!sha) {
return NULL;
}
+ sha->lock = NULL;
PyObject_GC_Track(sha);
return sha;
}
SHA256_dealloc(SHA256object *ptr)
{
Hacl_Streaming_SHA2_free_256(ptr->state);
+ if (ptr->lock != NULL) {
+ PyThread_free_lock(ptr->lock);
+ }
PyTypeObject *tp = Py_TYPE(ptr);
PyObject_GC_UnTrack(ptr);
PyObject_GC_Del(ptr);
SHA512_dealloc(SHA512object *ptr)
{
Hacl_Streaming_SHA2_free_512(ptr->state);
+ if (ptr->lock != NULL) {
+ PyThread_free_lock(ptr->lock);
+ }
PyTypeObject *tp = Py_TYPE(ptr);
PyObject_GC_UnTrack(ptr);
PyObject_GC_Del(ptr);
}
}
+ ENTER_HASHLIB(self);
SHA256copy(self, newobj);
+ LEAVE_HASHLIB(self);
return (PyObject *)newobj;
}
}
}
+ ENTER_HASHLIB(self);
SHA512copy(self, newobj);
+ LEAVE_HASHLIB(self);
return (PyObject *)newobj;
}
{
uint8_t digest[SHA256_DIGESTSIZE];
assert(self->digestsize <= SHA256_DIGESTSIZE);
+ ENTER_HASHLIB(self);
// HACL* performs copies under the hood so that self->state remains valid
// after this call.
Hacl_Streaming_SHA2_finish_256(self->state, digest);
+ LEAVE_HASHLIB(self);
return PyBytes_FromStringAndSize((const char *)digest, self->digestsize);
}
{
uint8_t digest[SHA512_DIGESTSIZE];
assert(self->digestsize <= SHA512_DIGESTSIZE);
+ ENTER_HASHLIB(self);
// HACL* performs copies under the hood so that self->state remains valid
// after this call.
Hacl_Streaming_SHA2_finish_512(self->state, digest);
+ LEAVE_HASHLIB(self);
return PyBytes_FromStringAndSize((const char *)digest, self->digestsize);
}
{
uint8_t digest[SHA256_DIGESTSIZE];
assert(self->digestsize <= SHA256_DIGESTSIZE);
+ ENTER_HASHLIB(self);
Hacl_Streaming_SHA2_finish_256(self->state, digest);
+ LEAVE_HASHLIB(self);
return _Py_strhex((const char *)digest, self->digestsize);
}
{
uint8_t digest[SHA512_DIGESTSIZE];
assert(self->digestsize <= SHA512_DIGESTSIZE);
+ ENTER_HASHLIB(self);
Hacl_Streaming_SHA2_finish_512(self->state, digest);
+ LEAVE_HASHLIB(self);
return _Py_strhex((const char *)digest, self->digestsize);
}
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
- update_256(self->state, buf.buf, buf.len);
+ if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->lock = PyThread_allocate_lock();
+ }
+ if (self->lock != NULL) {
+ Py_BEGIN_ALLOW_THREADS
+ PyThread_acquire_lock(self->lock, 1);
+ update_256(self->state, buf.buf, buf.len);
+ PyThread_release_lock(self->lock);
+ Py_END_ALLOW_THREADS
+ } else {
+ update_256(self->state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
Py_RETURN_NONE;
GET_BUFFER_VIEW_OR_ERROUT(obj, &buf);
- update_512(self->state, buf.buf, buf.len);
+ if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->lock = PyThread_allocate_lock();
+ }
+ if (self->lock != NULL) {
+ Py_BEGIN_ALLOW_THREADS
+ PyThread_acquire_lock(self->lock, 1);
+ update_512(self->state, buf.buf, buf.len);
+ PyThread_release_lock(self->lock);
+ Py_END_ALLOW_THREADS
+ } else {
+ update_512(self->state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
Py_RETURN_NONE;
return NULL;
}
if (string) {
- update_256(new->state, buf.buf, buf.len);
+ if (buf.len >= HASHLIB_GIL_MINSIZE) {
+ /* We do not initialize self->lock here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ Py_BEGIN_ALLOW_THREADS
+ update_256(new->state, buf.buf, buf.len);
+ Py_END_ALLOW_THREADS
+ } else {
+ update_256(new->state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
}
return NULL;
}
if (string) {
- update_256(new->state, buf.buf, buf.len);
+ if (buf.len >= HASHLIB_GIL_MINSIZE) {
+ /* We do not initialize self->lock here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ Py_BEGIN_ALLOW_THREADS
+ update_256(new->state, buf.buf, buf.len);
+ Py_END_ALLOW_THREADS
+ } else {
+ update_256(new->state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
}
return NULL;
}
if (string) {
- update_512(new->state, buf.buf, buf.len);
+ if (buf.len >= HASHLIB_GIL_MINSIZE) {
+ /* We do not initialize self->lock here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ Py_BEGIN_ALLOW_THREADS
+ update_512(new->state, buf.buf, buf.len);
+ Py_END_ALLOW_THREADS
+ } else {
+ update_512(new->state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
}
return NULL;
}
if (string) {
- update_512(new->state, buf.buf, buf.len);
+ if (buf.len >= HASHLIB_GIL_MINSIZE) {
+ /* We do not initialize self->lock here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ Py_BEGIN_ALLOW_THREADS
+ update_512(new->state, buf.buf, buf.len);
+ Py_END_ALLOW_THREADS
+ } else {
+ update_512(new->state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
}
typedef struct {
PyObject_HEAD
+ // Prevents undefined behavior via multiple threads entering the C API.
+ // The lock will be NULL before threaded access has been enabled.
+ PyThread_type_lock lock;
Hacl_Streaming_Keccak_state *hash_state;
} SHA3object;
if (newobj == NULL) {
return NULL;
}
+ newobj->lock = NULL;
return newobj;
}
if (data) {
GET_BUFFER_VIEW_OR_ERROR(data, &buf, goto error);
- sha3_update(self->hash_state, buf.buf, buf.len);
+ if (buf.len >= HASHLIB_GIL_MINSIZE) {
+ /* We do not initialize self->lock here as this is the constructor
+ * where it is not yet possible to have concurrent access. */
+ Py_BEGIN_ALLOW_THREADS
+ sha3_update(self->hash_state, buf.buf, buf.len);
+ Py_END_ALLOW_THREADS
+ } else {
+ sha3_update(self->hash_state, buf.buf, buf.len);
+ }
}
PyBuffer_Release(&buf);
SHA3_dealloc(SHA3object *self)
{
Hacl_Streaming_Keccak_free(self->hash_state);
+ if (self->lock != NULL) {
+ PyThread_free_lock(self->lock);
+ }
PyTypeObject *tp = Py_TYPE(self);
PyObject_Free(self);
Py_DECREF(tp);
if ((newobj = newSHA3object(Py_TYPE(self))) == NULL) {
return NULL;
}
+ ENTER_HASHLIB(self);
newobj->hash_state = Hacl_Streaming_Keccak_copy(self->hash_state);
+ LEAVE_HASHLIB(self);
return (PyObject *)newobj;
}
unsigned char digest[SHA3_MAX_DIGESTSIZE];
// This function errors out if the algorithm is Shake. Here, we know this
// not to be the case, and therefore do not perform error checking.
+ ENTER_HASHLIB(self);
Hacl_Streaming_Keccak_finish(self->hash_state, digest);
+ LEAVE_HASHLIB(self);
return PyBytes_FromStringAndSize((const char *)digest,
Hacl_Streaming_Keccak_hash_len(self->hash_state));
}
/*[clinic end generated code: output=75ad03257906918d input=2d91bb6e0d114ee3]*/
{
unsigned char digest[SHA3_MAX_DIGESTSIZE];
+ ENTER_HASHLIB(self);
Hacl_Streaming_Keccak_finish(self->hash_state, digest);
+ LEAVE_HASHLIB(self);
return _Py_strhex((const char *)digest,
Hacl_Streaming_Keccak_hash_len(self->hash_state));
}
{
Py_buffer buf;
GET_BUFFER_VIEW_OR_ERROUT(data, &buf);
- sha3_update(self->hash_state, buf.buf, buf.len);
+ if (self->lock == NULL && buf.len >= HASHLIB_GIL_MINSIZE) {
+ self->lock = PyThread_allocate_lock();
+ }
+ if (self->lock != NULL) {
+ Py_BEGIN_ALLOW_THREADS
+ PyThread_acquire_lock(self->lock, 1);
+ sha3_update(self->hash_state, buf.buf, buf.len);
+ PyThread_release_lock(self->lock);
+ Py_END_ALLOW_THREADS
+ } else {
+ sha3_update(self->hash_state, buf.buf, buf.len);
+ }
PyBuffer_Release(&buf);
Py_RETURN_NONE;
}