Skip to content

AP-24824: Addeds a refresh path in getCategories() so dev-mode extens…#70

Draft
xnhp wants to merge 1 commit intomasterfrom
bug/AP-24824-python-categories
Draft

AP-24824: Addeds a refresh path in getCategories() so dev-mode extens…#70
xnhp wants to merge 1 commit intomasterfrom
bug/AP-24824-python-categories

Conversation

@xnhp
Copy link

@xnhp xnhp commented Feb 13, 2026

…ions...

... (-Dknime.python.extension.config) re-populate categories on demand. Cache rebuild now re-derives nodes and categories from the supplier, ensuring category sets reflect dev-mode configs even if they were initialized before the config was picked up.

AP-24824 (Python node do not show up in correct category in Modern UI in Dev mode)

…ions...

... (-Dknime.python.extension.config) re-populate categories on demand. Cache
rebuild now re-derives nodes and categories from the supplier, ensuring category
sets reflect dev-mode configs even if they were initialized before the config
was picked up.

AP-24824 (Python node do not show up in correct category in Modern UI in Dev mode)
Copilot AI review requested due to automatic review settings February 13, 2026 11:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses AP-24824 by adding a refresh path so Python extension categories (and associated node/category caches) can be re-derived from the extension supplier on demand, improving correctness in dev-mode where custom extension config may be picked up after initial cache initialization.

Changes:

  • Store the extensionSupplier and introduce a refreshCaches() method to rebuild extensions/nodes/categories from the supplier.
  • Trigger cache refresh from getCategories() when custom extension paths are present (dev-mode scenario).
  • Add a helper (hasCustomExtensions()) based on PythonExtensionPreferences.

Comment on lines 183 to +186
public Collection<CategoryExtension> getCategories() {
if (hasCustomExtensions()) {
refreshCaches();
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In dev-mode, hasCustomExtensions() will stay true, so getCategories() will call refreshCaches() on every invocation. Since refreshCaches() re-parses extensions (see PurePythonNodeSetFactory.parseExtension -> PythonExtensionParser.parseExtension), this can add significant I/O/CPU overhead when the UI asks for categories repeatedly. Consider refreshing only when the custom extension path set has changed (cache a fingerprint/last-modified) or exposing an explicit invalidate mechanism.

Copilot uses AI. Check for mistakes.
Comment on lines +134 to +138
var extensions = m_extensionSupplier.get().collect(toMap(KnimeExtension::getId, Function.identity()));
m_extensions.clear();
m_extensions.putAll(extensions);
ALL_EXTENSIONS.putAll(extensions);
m_allNodes.clear();
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refreshCaches() updates ALL_EXTENSIONS via putAll() but never removes entries that are no longer provided by the supplier. If custom extension paths change during dev-mode, ALL_EXTENSIONS can retain stale KnimeExtension instances indefinitely (memory leak) and DynamicExtensionNodeFactory may still resolve removed extensions. Consider clearing ALL_EXTENSIONS first (or retaining only current keys) before repopulating.

Copilot uses AI. Check for mistakes.
Comment on lines +133 to 145
private synchronized void refreshCaches() {
var extensions = m_extensionSupplier.get().collect(toMap(KnimeExtension::getId, Function.identity()));
m_extensions.clear();
m_extensions.putAll(extensions);
ALL_EXTENSIONS.putAll(extensions);
m_allNodes.clear();
m_allCategories.clear();
for (var extension : extensions.values()) {
var extensionId = extension.getId();
extension.getNodes().forEach(n -> m_allNodes.put(new NodeId(extensionId, n.getId()), n));
extension.getCategories().forEach(c -> m_allCategories.add(c));
}
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refreshCaches() mutates HashMap/ArrayList instances (clear()+put/add) while other public methods (e.g., getNodeFactoryIds/getCategoryPath) read them without synchronization. Since getCategories() can now trigger refreshCaches(), concurrent access can lead to data races or ConcurrentModificationException. Consider rebuilding into new immutable maps/lists and atomically swapping references (e.g., volatile fields), or synchronizing readers on the same lock.

Copilot uses AI. Check for mistakes.
if (hasCustomExtensions()) {
refreshCaches();
}
return m_allCategories;
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCategories() returns the internal mutable m_allCategories list, and refreshCaches() clears/repopulates that same list instance. Any caller that keeps a reference or iterates while a refresh happens can observe unexpected mutations or hit ConcurrentModificationException. Prefer returning an unmodifiable snapshot (e.g., List.copyOf(...)) and avoid mutating a list that has been handed out.

Suggested change
return m_allCategories;
return List.copyOf(m_allCategories);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant