Skip to content

Commit f29a4d8

Browse files
committed
fix
1 parent 836d71f commit f29a4d8

File tree

2 files changed

+28
-1
lines changed

2 files changed

+28
-1
lines changed

msgspec/_core.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1886,14 +1886,14 @@ Meta_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) {
18861886
Meta *out = (Meta *)Meta_Type.tp_alloc(&Meta_Type, 0);
18871887
if (out == NULL) return NULL;
18881888

1889+
// set fields on Meta and increase their reference if they are not NULL
18891890
#define SET_FIELD(x) do { Py_XINCREF(x); out->x = x; } while(0)
18901891
SET_FIELD(gt);
18911892
SET_FIELD(ge);
18921893
SET_FIELD(lt);
18931894
SET_FIELD(le);
18941895
SET_FIELD(multiple_of);
18951896
SET_FIELD(pattern);
1896-
SET_FIELD(regex);
18971897
SET_FIELD(min_length);
18981898
SET_FIELD(max_length);
18991899
SET_FIELD(tz);
@@ -1903,6 +1903,11 @@ Meta_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) {
19031903
SET_FIELD(extra_json_schema);
19041904
SET_FIELD(extra);
19051905
#undef SET_FIELD
1906+
1907+
// set fields on Meta without increasing their reference
1908+
// regex was created by a PyObject_CallOneArg call, so refcount started out as 1; no need to increase
1909+
out->regex = regex;
1910+
19061911
return (PyObject *)out;
19071912
}
19081913

tests/test_struct_meta.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
"""Tests for the exposed StructMeta metaclass."""
2+
import gc
3+
import re
4+
import secrets
5+
import uuid
26

37
import pytest
48
import msgspec
@@ -362,3 +366,21 @@ class Container(metaclass=EncoderMeta):
362366
assert decoded.count == 1
363367
assert decoded.item.id == 123
364368
assert decoded.item.name == "test"
369+
370+
371+
def test_struct_meta_pattern_ref_leak():
372+
# ensure that we're not keeping around references to re.Pattern longer than necessary
373+
374+
# clear cache to get a baseline
375+
re.purge()
376+
377+
# use a random string to create a pattern, to ensure there can never be an overlap
378+
# with any cached pattern
379+
pattern_string = secrets.token_hex()
380+
msgspec.Meta(pattern=pattern_string)
381+
# purge cache and gc again
382+
re.purge()
383+
gc.collect()
384+
# there should be no re.Pattern any more with our pattern anymore. if there is, it's
385+
# being kept alive by some reference
386+
assert not any(o for o in gc.get_objects() if isinstance(o, re.Pattern) and o.pattern == pattern_string)

0 commit comments

Comments
 (0)