-
Notifications
You must be signed in to change notification settings - Fork 42
Release the GIL across libmaxminddb lookup calls #362
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -442,9 +442,18 @@ static int get_record(PyObject *self, PyObject *args, PyObject **record) { | |
| return -1; | ||
| } | ||
|
|
||
| // Release the GIL across the libmaxminddb tree walk. The walk only | ||
| // touches the read-only mmap and stack-local state, the rwlock is | ||
| // already held, and the module is declared Py_MOD_GIL_NOT_USED - so | ||
| // no Python state is accessed here. On systems where the mmdb's | ||
| // pages are not resident (cold cache, slow disk, memory pressure) | ||
| // an individual page-in can stall this call for seconds; without | ||
| // releasing the GIL, the entire interpreter stalls with it. | ||
| int mmdb_error = MMDB_SUCCESS; | ||
| MMDB_lookup_result_s result = | ||
| MMDB_lookup_sockaddr(mmdb, ip_address, &mmdb_error); | ||
| MMDB_lookup_result_s result; | ||
| Py_BEGIN_ALLOW_THREADS | ||
| result = MMDB_lookup_sockaddr(mmdb, ip_address, &mmdb_error); | ||
| Py_END_ALLOW_THREADS | ||
|
|
||
| if (mmdb_error != MMDB_SUCCESS) { | ||
| reader_release_read_lock(reader); | ||
|
|
@@ -478,8 +487,13 @@ static int get_record(PyObject *self, PyObject *args, PyObject **record) { | |
| return prefix_len; | ||
| } | ||
|
|
||
| // Same reasoning as above: read the data section from the mmap | ||
| // without holding the GIL. | ||
| MMDB_entry_data_list_s *entry_data_list = NULL; | ||
| int status = MMDB_get_entry_data_list(&result.entry, &entry_data_list); | ||
| int status; | ||
| Py_BEGIN_ALLOW_THREADS | ||
| status = MMDB_get_entry_data_list(&result.entry, &entry_data_list); | ||
| Py_END_ALLOW_THREADS | ||
|
Comment on lines
+494
to
+496
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to the previous lookup call, releasing the GIL here is unsafe in 'GIL-only' mode because the You should wrap this block in #ifndef MAXMINDDB_USE_GIL_ONLY
Py_BEGIN_ALLOW_THREADS
#endif
status = MMDB_get_entry_data_list(&result.entry, &entry_data_list);
#ifndef MAXMINDDB_USE_GIL_ONLY
Py_END_ALLOW_THREADS
#endif |
||
| if (status != MMDB_SUCCESS) { | ||
| reader_release_read_lock(reader); | ||
| char ipstr[INET6_ADDRSTRLEN] = {0}; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Releasing the GIL here is unsafe when the extension is compiled in 'GIL-only' mode (the default for standard Python builds, as defined on lines 24-26). In this mode, the internal
rwlockis a no-op, and the GIL is the only mechanism protecting themmdbstructure from being freed by a concurrentReader.close()call in another thread. If the GIL is released, a race condition occurs that can lead to a use-after-free crash.To fix this safely, wrap the block in
#ifndef MAXMINDDB_USE_GIL_ONLY. To enable this optimization for standard Python, you should also update the locking logic at the top of the file to use real locks (pthreads/Windows) even when the GIL is present.