diff --git a/source/jucePluginEditorLib/patchmanager/patchmanager.cpp b/source/jucePluginEditorLib/patchmanager/patchmanager.cpp index 04b2c1f8a..18db0f1fd 100644 --- a/source/jucePluginEditorLib/patchmanager/patchmanager.cpp +++ b/source/jucePluginEditorLib/patchmanager/patchmanager.cpp @@ -644,7 +644,16 @@ namespace jucePluginEditorLib::patchManager results.assign(_search.results.begin(), _search.results.end()); if(results.empty()) + { + // the search found no match, but it must still be cancelled/removed from the DB's + // search list, otherwise it leaks forever and every subsequent bank load / patch + // update has to scan it in DB::updateSearches, causing CPU usage to grow unbounded + runOnUiThread([this, handle] + { + cancelSearch(handle); + }); return; + } if(results.size() > 1) { diff --git a/source/jucePluginLib/patchdb/db.cpp b/source/jucePluginLib/patchdb/db.cpp index 4d520df1a..1a5211fce 100644 --- a/source/jucePluginLib/patchdb/db.cpp +++ b/source/jucePluginLib/patchdb/db.cpp @@ -480,7 +480,14 @@ namespace pluginLib::patchDB return; std::unique_lock lock(m_searchesMutex); - m_cancelledSearches.insert(_handle); + + // only remember the handle as cancelled if the search may still be picked up by executeSearch + // later (i.e. it's still queued/running). A search that has already completed will never be + // looked at again, so tracking it here would leak the handle in m_cancelledSearches forever. + const auto it = m_searches.find(_handle); + if(it != m_searches.end() && it->second->state != SearchState::Completed) + m_cancelledSearches.insert(_handle); + m_searches.erase(_handle); }