Skip to content

Commit 0804b2b

Browse files
committed
Reapply "Mutex in usages of PyFT2Font"
This reverts commit e3ad87c.
1 parent 4d3af12 commit 0804b2b

1 file changed

Lines changed: 34 additions & 4 deletions

File tree

‎src/ft2font_wrapper.cpp‎

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#include "ft2font.h"
1111

12+
#include <memory>
13+
#include <mutex>
1214
#include <set>
1315
#include <sstream>
1416
#include <unordered_map>
@@ -288,6 +290,18 @@ class PyFT2Font final : public FT2Font
288290
FT_StreamRec stream;
289291
py::list fallbacks;
290292

293+
// Serializes this object's mutating Python-facing methods so that concurrent
294+
// calls on the SAME FT2Font (under free-threading / py::mod_gil_not_used)
295+
// cannot corrupt its FreeType glyph slot, glyph list or image buffer. It is
296+
// taken ONLY at the wrapper boundary -- never inside FT2Font's C++ methods,
297+
// which also run during construction and fallback recursion -- so a single
298+
// lock is held per top-level call and it never nests (deadlock-free by
299+
// construction). Recursive to tolerate re-entry via Python callbacks such as
300+
// ft_glyph_warn(). Contract: one FT2Font (together with its fallback graph)
301+
// is used by a single thread at a time; matplotlib enforces this via the
302+
// thread-keyed cache in font_manager._get_font.
303+
std::recursive_mutex mutex;
304+
291305
~PyFT2Font()
292306
{
293307
// Because destructors are called from subclass up to base class, we need to
@@ -432,7 +446,9 @@ PyFT2Font_init(FT_Library ft2Library, py::object filename,
432446
std::back_inserter(fallback_fonts));
433447
}
434448

435-
auto self = new PyFT2Font(fallback_fonts, warn_if_used);
449+
// Own the object locally so it is released if any step below throws (e.g. a
450+
// corrupt font makes self->open() fail); pybind11 adopts it only on success.
451+
auto self = std::make_unique<PyFT2Font>(fallback_fonts, warn_if_used);
436452
self->set_kerning_factor(*kerning_factor);
437453

438454
if (fallback_list) {
@@ -476,7 +492,7 @@ PyFT2Font_init(FT_Library ft2Library, py::object filename,
476492

477493
self->open(ft2Library, open_args, face_index);
478494

479-
return self;
495+
return self.release();
480496
}
481497

482498
static py::object
@@ -616,6 +632,7 @@ PyFT2Font_set_text(PyFT2Font *self, std::u32string_view text, double angle = 0.0
616632
std::optional<std::vector<std::string>> features = std::nullopt,
617633
std::variant<FT2Font::LanguageType, std::string> languages_or_str = nullptr)
618634
{
635+
std::scoped_lock lock{self->mutex};
619636
std::vector<double> xys;
620637

621638
FT2Font::LanguageType languages;
@@ -674,6 +691,7 @@ static PyGlyph *
674691
PyFT2Font_load_char(PyFT2Font *self, long charcode,
675692
LoadFlags flags = LoadFlags::FORCE_AUTOHINT)
676693
{
694+
std::scoped_lock lock{self->mutex};
677695
bool fallback = true;
678696
FT2Font *ft_object = nullptr;
679697

@@ -712,6 +730,7 @@ static PyGlyph *
712730
PyFT2Font_load_glyph(PyFT2Font *self, FT_UInt glyph_index,
713731
LoadFlags flags = LoadFlags::FORCE_AUTOHINT)
714732
{
733+
std::scoped_lock lock{self->mutex};
715734
self->load_glyph(glyph_index, static_cast<FT_Int32>(flags));
716735

717736
return PyGlyph_from_FT2Font(self);
@@ -814,6 +833,7 @@ PyFT2Font_draw_glyph_to_bitmap(PyFT2Font *self, py::buffer &image,
814833
int xd, int yd,
815834
PyGlyph *glyph, bool antialiased = true)
816835
{
836+
std::scoped_lock lock{self->mutex};
817837
self->draw_glyph_to_bitmap(
818838
py::array_t<uint8_t, py::array::c_style>{image},
819839
xd, yd, glyph->glyphInd, antialiased);
@@ -1338,6 +1358,7 @@ PyFT2Font_layout(PyFT2Font *self, std::u32string text, LoadFlags flags,
13381358
std::optional<std::vector<std::string>> features = std::nullopt,
13391359
std::variant<FT2Font::LanguageType, std::string> languages_or_str = nullptr)
13401360
{
1361+
std::scoped_lock lock{self->mutex};
13411362
const auto load_flags = static_cast<FT_Int32>(flags);
13421363

13431364
FT2Font::LanguageType languages;
@@ -1584,7 +1605,12 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
15841605
"face_index"_a=0, "_fallback_list"_a=py::none(),
15851606
"_kerning_factor"_a=py::none(), "_warn_if_used"_a=false,
15861607
PyFT2Font_init__doc__)
1587-
.def("clear", &PyFT2Font::clear, PyFT2Font_clear__doc__)
1608+
.def("clear",
1609+
[](PyFT2Font& self) {
1610+
std::scoped_lock lock{self.mutex};
1611+
self.clear();
1612+
},
1613+
PyFT2Font_clear__doc__)
15881614
.def("set_size", &PyFT2Font::set_size, "ptsize"_a, "dpi"_a,
15891615
PyFT2Font_set_size__doc__)
15901616
.def("_set_transform", &PyFT2Font::_set_transform, "matrix"_a, "delta"_a,
@@ -1615,7 +1641,11 @@ PYBIND11_MODULE(ft2font, m, py::mod_gil_not_used())
16151641
.def("get_bitmap_offset", &PyFT2Font::get_bitmap_offset,
16161642
PyFT2Font_get_bitmap_offset__doc__)
16171643
.def("get_descent", &PyFT2Font::get_descent, PyFT2Font_get_descent__doc__)
1618-
.def("draw_glyphs_to_bitmap", &PyFT2Font::draw_glyphs_to_bitmap,
1644+
.def("draw_glyphs_to_bitmap",
1645+
[](PyFT2Font& self, bool antialiased) {
1646+
std::scoped_lock lock{self.mutex};
1647+
self.draw_glyphs_to_bitmap(antialiased);
1648+
},
16191649
py::kw_only(), "antialiased"_a=true,
16201650
PyFT2Font_draw_glyphs_to_bitmap__doc__)
16211651
.def("draw_glyph_to_bitmap", &PyFT2Font_draw_glyph_to_bitmap,

0 commit comments

Comments
 (0)