Skip to content

Commit cfc7f4e

Browse files
Use a singleton class to track memoryviews
This enables memoryviews of object data to be released when the object is deleted. One more possible segfault cause eliminated!
1 parent 814a07b commit cfc7f4e

27 files changed

Lines changed: 10879 additions & 240 deletions

src/interface/shared/buffers.i

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -79,34 +79,55 @@ INPUT_BUFFER_RO(buf_type, len_type)
7979
%}
8080
%enddef // OUTPUT_BUFFER_RW
8181

82+
// Import exiv2.utilities.view_manager
83+
%fragment("_import_view_manager_decl", "header") {
84+
static PyObject* view_manager = NULL;
85+
}
86+
%fragment("_import_view_manager", "init",
87+
fragment="_import_view_manager_decl") {
88+
{
89+
PyObject* mod = PyImport_ImportModule("exiv2.utilities");
90+
if (!mod)
91+
return INIT_ERROR_RETURN;
92+
view_manager = PyObject_GetAttrString(mod, "view_manager");
93+
if (!view_manager) {
94+
PyErr_SetString(
95+
PyExc_RuntimeError,
96+
"Import error: exiv2.utilities.view_manager not found.");
97+
return INIT_ERROR_RETURN;
98+
}
99+
}
100+
}
101+
82102
// Functions to store references to memoryview objects and release them
83-
%fragment("memoryview_funcs", "header", fragment="private_data") {
103+
%fragment("memoryview_funcs", "header", fragment="private_data",
104+
fragment="_import_view_manager") {
84105
static int store_view(PyObject* py_self, PyObject* view,
85106
PyObject* callback=NULL) {
86-
PyObject* views = private_store_get(py_self, "views");
87-
if (!views) {
88-
views = PyList_New(0);
89-
private_store_set(py_self, "views", views);
90-
Py_DECREF(views);
107+
PyObject* view_ref = PyWeakref_NewRef(view, callback);
108+
if (!view_ref)
109+
return -1;
110+
PyObject* marker = private_store_get(py_self, "marker");
111+
if (!marker) {
112+
// Marker is any weakrefable object.
113+
marker = PySet_New(NULL);
114+
if (!marker)
115+
return -1;
116+
int error = private_store_set(py_self, "marker", marker);
117+
Py_DECREF(marker);
118+
if (error)
119+
return -1;
91120
}
92-
PyObject* ref = PyWeakref_NewRef(view, callback);
93-
if (!ref)
121+
PyObject* OK = PyObject_CallMethod(
122+
view_manager, "store_view", "(OO)", marker, view_ref);
123+
Py_DECREF(view_ref);
124+
if (!OK)
94125
return -1;
95-
int result = PyList_Append(views, ref);
96-
Py_DECREF(ref);
97-
return result;
126+
Py_DECREF(OK);
127+
return 0;
98128
};
99129
static int release_views(PyObject* py_self) {
100-
PyObject* views = private_store_get(py_self, "views");
101-
if (!views)
102-
return 0;
103-
PyObject* view = NULL;
104-
for (Py_ssize_t idx = PyList_Size(views); idx > 0; idx--) {
105-
view = PyWeakref_GetObject(PyList_GetItem(views, idx - 1));
106-
if (PyMemoryView_Check(view))
107-
Py_XDECREF(PyObject_CallMethod(view, "release", NULL));
108-
}
109-
private_store_del(py_self, "views");
130+
private_store_del(py_self, "marker");
110131
return 0;
111132
};
112133
}

src/interface/shared/keep_reference.i

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,11 @@ static int private_store_del(PyObject* py_self, const char* name) {
5555
PyObject* dict = _get_store(py_self, false);
5656
if (!dict)
5757
return 0;
58-
if (PyDict_DelItemString(dict, name))
59-
PyErr_Clear();
60-
return 0;
58+
if (!PyDict_GetItemString(dict, name))
59+
return 0;
60+
int result = PyDict_DelItemString(dict, name);
61+
Py_DECREF(dict);
62+
return result;
6163
};
6264
}
6365

src/interface/utilities.i

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// python-exiv2 - Python interface to libexiv2
2+
// http://github.com/jim-easterbrook/python-exiv2
3+
// Copyright (C) 2025 Jim Easterbrook jim@jim-easterbrook.me.uk
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
%module(package="exiv2") utilities
19+
20+
%include "shared/preamble.i"
21+
22+
23+
// Class to store and release memoryviews
24+
%typemap(in, numinputs=0) PyObject* py_self {$1 = self;}
25+
%inline %{
26+
class ViewManager {
27+
private:
28+
PyObject* view_list;
29+
public:
30+
ViewManager() {
31+
view_list = PyList_New(0);
32+
};
33+
PyObject* store_view(PyObject* py_self, PyObject* marker,
34+
PyObject* view_ref) {
35+
release_views(NULL);
36+
int error = 0;
37+
PyObject* callback = PyObject_GetAttrString(
38+
py_self, "release_views");
39+
if (!callback)
40+
return NULL;
41+
PyObject* marker_ref = PyWeakref_NewRef(marker, callback);
42+
Py_DECREF(callback);
43+
PyObject* details = PyTuple_Pack(2, marker_ref, view_ref);
44+
Py_XDECREF(marker_ref);
45+
if (!details)
46+
return NULL;
47+
error = PyList_Append(view_list, details);
48+
Py_DECREF(details);
49+
if (error)
50+
return NULL;
51+
return SWIG_Py_Void();
52+
};
53+
void release_views(PyObject* marker) {
54+
// Release any views of object that owned marker
55+
PyObject* details = NULL;
56+
PyObject* view_ref = NULL;
57+
PyObject* marker_ref = NULL;
58+
PyObject* view = NULL;
59+
for (Py_ssize_t idx = PyList_Size(view_list); idx > 0; idx--) {
60+
details = PyList_GetItem(view_list, idx - 1);
61+
marker_ref = PyTuple_GET_ITEM(details, 0);
62+
view_ref = PyTuple_GET_ITEM(details, 1);
63+
view = PyWeakref_GetObject(view_ref);
64+
if (view != Py_None) {
65+
if (PyWeakref_GetObject(marker_ref) != Py_None)
66+
continue;
67+
Py_XDECREF(PyObject_CallMethod(view, "release", NULL));
68+
}
69+
PyList_SetSlice(view_list, idx - 1, idx, NULL);
70+
}
71+
};
72+
};
73+
%}
74+
75+
// Create one ViewManager instance to be used in all modules
76+
%constant PyObject* view_manager = PyObject_CallMethod(
77+
m, "ViewManager", NULL);

src/swig-0_27_7/basicio_wrap.cxx

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4524,38 +4524,43 @@ static int private_store_del(PyObject* py_self, const char* name) {
45244524
PyObject* dict = _get_store(py_self, false);
45254525
if (!dict)
45264526
return 0;
4527-
if (PyDict_DelItemString(dict, name))
4528-
PyErr_Clear();
4529-
return 0;
4527+
if (!PyDict_GetItemString(dict, name))
4528+
return 0;
4529+
int result = PyDict_DelItemString(dict, name);
4530+
Py_DECREF(dict);
4531+
return result;
45304532
};
45314533

45324534

4535+
static PyObject* view_manager = NULL;
4536+
4537+
45334538
static int store_view(PyObject* py_self, PyObject* view,
45344539
PyObject* callback=NULL) {
4535-
PyObject* views = private_store_get(py_self, "views");
4536-
if (!views) {
4537-
views = PyList_New(0);
4538-
private_store_set(py_self, "views", views);
4539-
Py_DECREF(views);
4540-
}
4541-
PyObject* ref = PyWeakref_NewRef(view, callback);
4542-
if (!ref)
4540+
PyObject* view_ref = PyWeakref_NewRef(view, callback);
4541+
if (!view_ref)
45434542
return -1;
4544-
int result = PyList_Append(views, ref);
4545-
Py_DECREF(ref);
4546-
return result;
4543+
PyObject* marker = private_store_get(py_self, "marker");
4544+
if (!marker) {
4545+
// Marker is any weakrefable object.
4546+
marker = PySet_New(NULL);
4547+
if (!marker)
4548+
return -1;
4549+
int error = private_store_set(py_self, "marker", marker);
4550+
Py_DECREF(marker);
4551+
if (error)
4552+
return -1;
4553+
}
4554+
PyObject* OK = PyObject_CallMethod(
4555+
view_manager, "store_view", "(OO)", marker, view_ref);
4556+
Py_DECREF(view_ref);
4557+
if (!OK)
4558+
return -1;
4559+
Py_DECREF(OK);
4560+
return 0;
45474561
};
45484562
static int release_views(PyObject* py_self) {
4549-
PyObject* views = private_store_get(py_self, "views");
4550-
if (!views)
4551-
return 0;
4552-
PyObject* view = NULL;
4553-
for (Py_ssize_t idx = PyList_Size(views); idx > 0; idx--) {
4554-
view = PyWeakref_GetObject(PyList_GetItem(views, idx - 1));
4555-
if (PyMemoryView_Check(view))
4556-
Py_XDECREF(PyObject_CallMethod(view, "release", NULL));
4557-
}
4558-
private_store_del(py_self, "views");
4563+
private_store_del(py_self, "marker");
45594564
return 0;
45604565
};
45614566

@@ -6997,6 +7002,20 @@ SWIG_init(void) {
69977002
}
69987003
}
69997004

7005+
7006+
{
7007+
PyObject* mod = PyImport_ImportModule("exiv2.utilities");
7008+
if (!mod)
7009+
return INIT_ERROR_RETURN;
7010+
view_manager = PyObject_GetAttrString(mod, "view_manager");
7011+
if (!view_manager) {
7012+
PyErr_SetString(
7013+
PyExc_RuntimeError,
7014+
"Import error: exiv2.utilities.view_manager not found.");
7015+
return INIT_ERROR_RETURN;
7016+
}
7017+
}
7018+
70007019
SWIG_Python_SetConstant(d, d == md ? public_interface : NULL, "Position",_create_enum_Exiv2_BasicIo_Position(
70017020
"Position", "Seek starting positions.", _get_enum_list(0, "beg",Exiv2::BasicIo::beg,"cur",Exiv2::BasicIo::cur,"end",Exiv2::BasicIo::end, NULL)));
70027021
builtin_base_count = 0;

src/swig-0_27_7/exif_wrap.cxx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4466,9 +4466,11 @@ static int private_store_del(PyObject* py_self, const char* name) {
44664466
PyObject* dict = _get_store(py_self, false);
44674467
if (!dict)
44684468
return 0;
4469-
if (PyDict_DelItemString(dict, name))
4470-
PyErr_Clear();
4471-
return 0;
4469+
if (!PyDict_GetItemString(dict, name))
4470+
return 0;
4471+
int result = PyDict_DelItemString(dict, name);
4472+
Py_DECREF(dict);
4473+
return result;
44724474
};
44734475

44744476

src/swig-0_27_7/image_wrap.cxx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4668,9 +4668,11 @@ static int private_store_del(PyObject* py_self, const char* name) {
46684668
PyObject* dict = _get_store(py_self, false);
46694669
if (!dict)
46704670
return 0;
4671-
if (PyDict_DelItemString(dict, name))
4672-
PyErr_Clear();
4673-
return 0;
4671+
if (!PyDict_GetItemString(dict, name))
4672+
return 0;
4673+
int result = PyDict_DelItemString(dict, name);
4674+
Py_DECREF(dict);
4675+
return result;
46744676
};
46754677

46764678

src/swig-0_27_7/iptc_wrap.cxx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4455,9 +4455,11 @@ static int private_store_del(PyObject* py_self, const char* name) {
44554455
PyObject* dict = _get_store(py_self, false);
44564456
if (!dict)
44574457
return 0;
4458-
if (PyDict_DelItemString(dict, name))
4459-
PyErr_Clear();
4460-
return 0;
4458+
if (!PyDict_GetItemString(dict, name))
4459+
return 0;
4460+
int result = PyDict_DelItemString(dict, name);
4461+
Py_DECREF(dict);
4462+
return result;
44614463
};
44624464

44634465

src/swig-0_27_7/preview_wrap.cxx

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5790,38 +5790,43 @@ static int private_store_del(PyObject* py_self, const char* name) {
57905790
PyObject* dict = _get_store(py_self, false);
57915791
if (!dict)
57925792
return 0;
5793-
if (PyDict_DelItemString(dict, name))
5794-
PyErr_Clear();
5795-
return 0;
5793+
if (!PyDict_GetItemString(dict, name))
5794+
return 0;
5795+
int result = PyDict_DelItemString(dict, name);
5796+
Py_DECREF(dict);
5797+
return result;
57965798
};
57975799

57985800

5801+
static PyObject* view_manager = NULL;
5802+
5803+
57995804
static int store_view(PyObject* py_self, PyObject* view,
58005805
PyObject* callback=NULL) {
5801-
PyObject* views = private_store_get(py_self, "views");
5802-
if (!views) {
5803-
views = PyList_New(0);
5804-
private_store_set(py_self, "views", views);
5805-
Py_DECREF(views);
5806-
}
5807-
PyObject* ref = PyWeakref_NewRef(view, callback);
5808-
if (!ref)
5806+
PyObject* view_ref = PyWeakref_NewRef(view, callback);
5807+
if (!view_ref)
58095808
return -1;
5810-
int result = PyList_Append(views, ref);
5811-
Py_DECREF(ref);
5812-
return result;
5809+
PyObject* marker = private_store_get(py_self, "marker");
5810+
if (!marker) {
5811+
// Marker is any weakrefable object.
5812+
marker = PySet_New(NULL);
5813+
if (!marker)
5814+
return -1;
5815+
int error = private_store_set(py_self, "marker", marker);
5816+
Py_DECREF(marker);
5817+
if (error)
5818+
return -1;
5819+
}
5820+
PyObject* OK = PyObject_CallMethod(
5821+
view_manager, "store_view", "(OO)", marker, view_ref);
5822+
Py_DECREF(view_ref);
5823+
if (!OK)
5824+
return -1;
5825+
Py_DECREF(OK);
5826+
return 0;
58135827
};
58145828
static int release_views(PyObject* py_self) {
5815-
PyObject* views = private_store_get(py_self, "views");
5816-
if (!views)
5817-
return 0;
5818-
PyObject* view = NULL;
5819-
for (Py_ssize_t idx = PyList_Size(views); idx > 0; idx--) {
5820-
view = PyWeakref_GetObject(PyList_GetItem(views, idx - 1));
5821-
if (PyMemoryView_Check(view))
5822-
Py_XDECREF(PyObject_CallMethod(view, "release", NULL));
5823-
}
5824-
private_store_del(py_self, "views");
5829+
private_store_del(py_self, "marker");
58255830
return 0;
58265831
};
58275832

@@ -8860,6 +8865,20 @@ SWIG_init(void) {
88608865

88618866
/* type 'Exiv2::PreviewImage' */
88628867
d = PyDict_New();
8868+
8869+
{
8870+
PyObject* mod = PyImport_ImportModule("exiv2.utilities");
8871+
if (!mod)
8872+
return INIT_ERROR_RETURN;
8873+
view_manager = PyObject_GetAttrString(mod, "view_manager");
8874+
if (!view_manager) {
8875+
PyErr_SetString(
8876+
PyExc_RuntimeError,
8877+
"Import error: exiv2.utilities.view_manager not found.");
8878+
return INIT_ERROR_RETURN;
8879+
}
8880+
}
8881+
88638882
builtin_base_count = 0;
88648883
builtin_bases[builtin_base_count] = NULL;
88658884
PyDict_SetItemString(d, "this", this_descr);

0 commit comments

Comments
 (0)