Skip to content

Commit 6f44d81

Browse files
fix(filter_protocol): route live FilterTransform instances via static catalogue
thunkCreate inserted the just-minted shared_ptr<FilterTransform> into a per-instance live_instances_ map, but thunkDeleteInstance (a free function — the per-id deleter handed back to plugins cannot capture context) erased from the static catalogue() that nothing inserted into. Net effect: every transform instance leaked forever, and the library_owner shared_ptr ref that pins the plugin DSO never dropped — a dialog refresh (which creates one instance per registered builtin, 12 of them) was an unbounded leak. Route both insert and delete through catalogue() so the lifecycle is consistent. Drop the dead live_instances_ member.
1 parent ae50933 commit 6f44d81

1 file changed

Lines changed: 11 additions & 7 deletions

File tree

pj_plugins/include/pj_plugins/host/filter_registry_host.hpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,16 @@ class FilterRegistryHost {
157157
}
158158
// The cross-DSO contract owns the raw pointer by handle. Stash the
159159
// shared_ptr so its deleter (which carries library_owner) survives until
160-
// the caller releases the handle via the per-id deleter.
160+
// the caller releases the handle via the per-id deleter. Must route via
161+
// catalogue() — the static map that thunkDeleteInstance erases from —
162+
// because the per-id deleter handed back by thunkLookupDeleter is a free
163+
// function and cannot capture `self`. An earlier draft of this file
164+
// populated a per-instance map (self.live_instances_) that thunkDelete
165+
// never erased from, leaking every instance forever.
161166
auto* raw = sp.get();
162167
{
163168
std::lock_guard<std::mutex> lock(self.mutex_);
164-
self.live_instances_.emplace(raw, std::move(sp));
169+
catalogue().emplace(raw, std::move(sp));
165170
}
166171
return reinterpret_cast<PJ_filter_transform_t*>(raw);
167172
}
@@ -227,11 +232,10 @@ class FilterRegistryHost {
227232
std::mutex mutex_;
228233
LibraryOwnerResolver resolver_;
229234
std::vector<std::string> cached_id_views_;
230-
// Bridges from raw FilterTransform* (what crosses the C ABI) back to the
231-
// shared_ptr<FilterTransform> the factory minted (its deleter carries the
232-
// library_owner). When the plugin frees the handle, we drop the shared_ptr
233-
// — that runs the deleter and the DSO ref drops.
234-
std::unordered_map<sdk::FilterTransform*, std::shared_ptr<sdk::FilterTransform>> live_instances_;
235+
// Live-instance bridge from raw FilterTransform* to the shared_ptr the
236+
// factory minted is stored in the static catalogue() above, not here — the
237+
// per-id deleter handed back by thunkLookupDeleter is a free function and
238+
// can only reach the static.
235239
};
236240

237241
} // namespace PJ

0 commit comments

Comments
 (0)