Skip to content

Commit 9f17a70

Browse files
committed
Small fix for thread safety and build actual free-threading enabled wheels
1 parent e50aa73 commit 9f17a70

2 files changed

Lines changed: 27 additions & 15 deletions

File tree

python_bindings/pyfastpfor.cc

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* PyFastPFOR
33
*
44
* Python bindings for the FastPFOR library:
5-
* https://github.com/lemire/FastPFor
5+
* https://github.com/lemire/FastPFor
66
*
77
* This code is released under the
88
* Apache License Version 2.0 http://www.apache.org/licenses/.
@@ -11,6 +11,7 @@
1111

1212
#include <cstdint>
1313
#include <iostream>
14+
#include <mutex>
1415
#include <string>
1516

1617
#include <pybind11/pybind11.h>
@@ -27,6 +28,9 @@ using namespace FastPForLib;
2728

2829
const char * module_name = "pyfastpfor";
2930

31+
// encodeArray/decodeArray release the GIL for the duration of the call; mutex_
32+
// serializes concurrent calls on the same instance since the underlying
33+
// codec's scratch state isn't otherwise safe for concurrent use.
3034
struct IntegerCODECWrapper {
3135
public:
3236
IntegerCODECWrapper(const std::string& codecName) {
@@ -36,13 +40,14 @@ struct IntegerCODECWrapper {
3640
py::array_t<uint32_t, py::array::c_style> input, size_t inputSize,
3741
py::array_t<uint32_t, py::array::c_style> output, size_t outputSize) {
3842
py::gil_scoped_release l;
43+
std::lock_guard<std::mutex> lock(mutex_);
3944

4045
const uint32_t* inpBuff = input.data();
4146

4247
uint32_t* outBuff = output.mutable_data();
4348
size_t compSize = outputSize;
4449

45-
codec_->encodeArray(inpBuff, inputSize,
50+
codec_->encodeArray(inpBuff, inputSize,
4651
outBuff, compSize);
4752

4853
return compSize;
@@ -51,6 +56,7 @@ struct IntegerCODECWrapper {
5156
py::array_t<uint32_t, py::array::c_style> input, size_t inputSize,
5257
py::array_t<uint32_t, py::array::c_style> output, size_t outputSize) {
5358
py::gil_scoped_release l;
59+
std::lock_guard<std::mutex> lock(mutex_);
5460

5561
const uint32_t* inpBuff = input.data();
5662

@@ -64,16 +70,21 @@ struct IntegerCODECWrapper {
6470
private:
6571
CODECFactory factory;
6672
IntegerCODEC* codec_;
73+
std::mutex mutex_;
6774
};
6875

69-
/*
70-
* PYBIND11_MODULE is a replacement for PYBIND11_PLUGIN
76+
/*
77+
* PYBIND11_MODULE is a replacement for PYBIND11_PLUGIN
7178
* introduced in Pybind 2.2. However, we don't require
72-
* Pybind to be >= 2.0 so we attempt to support older
79+
* Pybind to be >= 2.0 so we attempt to support older
7380
* Pybind versions as well.
7481
*/
7582
#ifdef PYBIND11_MODULE
76-
PYBIND11_MODULE(pyfastpfor, m) {
83+
// mod_gil_not_used (pybind11 >= 2.13) declares this module free-threading
84+
// safe: every codec object owns its own private CODECFactory/codec state
85+
// (no cross-instance sharing), and the module-scope statics in vsencoding.h
86+
// are populated once at load and only read afterward.
87+
PYBIND11_MODULE(pyfastpfor, m, py::mod_gil_not_used()) {
7788
m.doc() = "Python Bindings for FastPFor library (fast integer compression).";
7889
#else
7990
PYBIND11_PLUGIN(pyfastpfor) {
@@ -94,7 +105,7 @@ PYBIND11_PLUGIN(pyfastpfor) {
94105
[](const std::string & codecName) {
95106
// We know that FastPFor will keep this shared pointer alive forever
96107
// so it is safe just to reference codec
97-
return py::cast(new IntegerCODECWrapper(codecName),
108+
return py::cast(new IntegerCODECWrapper(codecName),
98109
py::return_value_policy::take_ownership);
99110
},
100111
py::arg("codecName"),
@@ -204,16 +215,16 @@ PYBIND11_PLUGIN(pyfastpfor) {
204215

205216
void exportCodecs(py::module& m) {
206217
py::class_<IntegerCODECWrapper>(m, "IntegerCODEC")
207-
.def("encodeArray", &IntegerCODECWrapper::encodeArray,
208-
py::arg("input"), py::arg("inputSize"),
218+
.def("encodeArray", &IntegerCODECWrapper::encodeArray,
219+
py::arg("input"), py::arg("inputSize"),
209220
py::arg("output"), py::arg("outputSize"),
210221
"Compress input array.\n\n"
211222
"Parameters\n"
212223
"----------\n"
213224
"input: numpy C-style contiguous array to be compressed, e.g.:\n"
214225
" input = numpy.array(range(256), dtype = np.uint32).ravel()\n"
215226
"inputSize: a number of integers to compress: it can be less than\n"
216-
" than the total number of integers in the numpy array.\n"
227+
" than the total number of integers in the numpy array.\n"
217228
"output: numpy C-style contiguous array with compressed data, e.g.:\n"
218229
" output = np.zeros(buffSize, dtype = np.uint32).ravel()\n"
219230
"outputSize: a capacity of the output buffer: it can be less than\n"
@@ -223,15 +234,15 @@ void exportCodecs(py::module& m) {
223234
"----------\n"
224235
" A number of integers in the compressed output.")
225236
.def("decodeArray", &IntegerCODECWrapper::decodeArray,
226-
py::arg("input"), py::arg("inputSize"),
237+
py::arg("input"), py::arg("inputSize"),
227238
py::arg("output"), py::arg("outputSize"),
228239
"Uncompress input array.\n\n"
229240
"Parameters\n"
230241
"----------\n"
231242
"input: numpy C-style contiguous array to be uncompressed, e.g.:\n"
232243
" input = numpy.array(range(256), dtype = np.uint32).ravel()\n"
233244
"inputSize: a number of integers to compress: it can be less than\n"
234-
" than the total number of integers in the numpy array.\n"
245+
" than the total number of integers in the numpy array.\n"
235246
"output: numpy C-style contiguous array with compressed data, e.g.:\n"
236247
" output = np.zeros(buffSize, dtype = np.uint32).ravel()\n"
237248
"outputSize: a capacity of the output buffer: it can be less than\n"
@@ -243,4 +254,3 @@ void exportCodecs(py::module& m) {
243254
)
244255
;
245256
}
246-

python_bindings/pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[build-system]
2-
requires = ["setuptools>=77.0.0", "wheel", "pybind11>=2.4", "numpy"]
2+
requires = ["setuptools>=77.0.0", "wheel", "pybind11>=2.13", "numpy"]
33
build-backend = "setuptools.build_meta"
44

55
[project]
@@ -45,7 +45,9 @@ py-modules = []
4545

4646
[tool.cibuildwheel]
4747
# Skip PyPy (the pybind11 extension targets CPython) and the slow/rare 32-bit
48-
# and musllinux targets to keep the wheel matrix lean.
48+
# and musllinux targets to keep the wheel matrix lean. Free-threaded ("t")
49+
# builds are included: the module declares py::mod_gil_not_used() (see
50+
# pyfastpfor.cc).
4951
skip = ["pp*", "*-musllinux*", "*_i686", "*-win32"]
5052
build-frontend = "build"
5153
# Published wheels must run on any CPU of the target architecture, so build with

0 commit comments

Comments
 (0)