EnxDev commented on code in PR #40441:
URL: https://github.com/apache/superset/pull/40441#discussion_r3340973082


##########
superset-frontend/src/extensions/ExtensionsStartup.tsx:
##########
@@ -73,21 +113,25 @@ const ExtensionsStartup: React.FC<{ children?: 
React.ReactNode }> = ({
       authentication,
       core,
       commands,
+      dashboard,
+      dataset,
       editors,
+      explore,
       extensions,
       menus,
+      navigation,
       sqlLab,
       views,
     };
 
-    const setup = async () => {
-      if (isFeatureEnabled(FeatureFlag.EnableExtensions)) {
-        await ExtensionsLoader.getInstance().initializeExtensions();
-      }
-      setInitialized(true);
-    };
+    // Render the host immediately; extension bundles load in the background.
+    // ChatbotMount re-resolves reactively once the chatbot extension registers
+    // (via subscribeToLocation), so the bubble appears without blocking the 
UI.

Review Comment:
   subscribeToLocation was renamed to the registry-wide subscribeToRegistry / 
getRegistryVersion (used by ChatbotMount via useSyncExternalStore); the comment 
was just stale. Updated it to match.



##########
superset-frontend/src/extensions/ExtensionsLoader.ts:
##########
@@ -88,23 +94,40 @@ class ExtensionsLoader {
   public async initializeExtension(extension: Extension) {
     try {
       if (extension.remoteEntry) {
-        await this.loadModule(extension);
+        const disposables = await this.loadModule(extension);
+        this.extensionDisposables.set(extension.id, disposables);
       }
       this.extensionIndex.set(extension.id, extension);
     } catch (error) {
       logging.error(
         `Failed to initialize extension ${extension.name}\n`,
         error,
       );
+      store.dispatch(
+        addDangerToast(t('Extension "%s" failed to load.', extension.name)),
+      );
+    }
+  }
+
+  /**
+   * Deactivates an extension by disposing all of its registered contributions
+   * and removing it from the index.
+   */
+  public deactivateExtension(id: string): void {
+    const disposables = this.extensionDisposables.get(id);
+    if (disposables) {
+      disposables.forEach(dispose => dispose());
+      this.extensionDisposables.delete(id);
     }
+    this.extensionIndex.delete(id);
   }

Review Comment:
   Good catch; that was true, and it was a real limitation. Disposables were 
only collected by the window.superset registrar-wrapping that's active 
synchronously during module evaluation, so anything an extension registered 
after an await (or in a timer/event callback) escaped tracking and leaked on 
deactivation.
   
   Fixed by introducing an activate(context) contract: the extension pushes 
each Disposable onto context.subscriptions, which the host owns for the 
extension's whole lifetime. Because identity is tied to the context object 
rather than a synchronous time window, async registrations are tracked the same 
as sync ones. The registrar-wrapping is retained as a fallback for legacy 
side-effect-only extensions (still sync-only, and documented as such). Added a 
test that registers after an await inside activate and asserts it's disposed on 
deactivation.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to