codeant-ai-for-open-source[bot] commented on code in PR #40084:
URL: https://github.com/apache/superset/pull/40084#discussion_r3525765165


##########
superset/extensions/api.py:
##########
@@ -169,7 +169,7 @@ def get(self, publisher: str, name: str, **kwargs: Any) -> 
Response:
 
     @protect()
     @safe
-    @expose("/<publisher>/<name>/<file>", methods=("GET",))
+    @expose("/<publisher>/<name>/<path:file>", methods=("GET",))

Review Comment:
   **Suggestion:** Changing the route to accept nested file paths introduces an 
integration mismatch: extension asset requests with subdirectories now bypass 
the cache middleware pattern that still matches only single-segment filenames, 
so `Vary: Cookie` is not stripped for those responses and shared/browser 
caching is degraded. Update the middleware asset-path regex to support nested 
paths so the new route behavior remains cache-consistent. [cache]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Nested extension assets retain Vary: Cookie header.
   - ⚠️ Shared caches cannot reuse nested extension asset responses.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. In `superset/app.py:23-87`, call `create_app()`, which wraps 
`app.wsgi_app` with
   `ExtensionCacheMiddleware` at lines 61-64 so every request passes through 
the cache
   middleware before routing.
   
   2. Load an extension bundle whose frontend assets include nested paths under
   `frontend/dist`, which are accepted by `FRONTEND_REGEX` in
   `superset/extensions/utils.py:9-15` and mapped into `frontend[...]` by
   `get_loaded_extension()` at `superset/extensions/utils.py:190-193`.
   
   3. Observe that extension frontend URLs are constructed with the asset path 
in
   `build_extension_data()` at `superset/extensions/utils.py:213-235`, e.g.
   `/api/v1/extensions/{publisher}/{name}/{frontend.remoteEntry}`, and that the 
asset route
   `content()` in `superset/extensions/api.py:170-233` is exposed at line 172 
using a Flask
   `path` converter to accept nested segments.
   
   4. Issue an HTTP GET to a nested asset like
   `/api/v1/extensions/acme/my-ext/nested/chunk.wasm`, which is handled by
   `ExtensionsRestApi.content()` (api.py:172-233) but whose `PATH_INFO` does 
not match
   `_ASSET_PATH_RE` (single-segment file regex) in
   `superset/extensions/cache_middleware.py:26-28`, so 
`ExtensionCacheMiddleware.__call__()`
   (lines 40-73) bypasses Vary-header stripping and leaves `Vary: Cookie` in 
the response,
   degrading shared/browser caching for nested assets.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=37aa7ebcefad45e5bc26d736c32da79c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=37aa7ebcefad45e5bc26d736c32da79c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/extensions/api.py
   **Line:** 172:172
   **Comment:**
        *Cache: Changing the route to accept nested file paths introduces an 
integration mismatch: extension asset requests with subdirectories now bypass 
the cache middleware pattern that still matches only single-segment filenames, 
so `Vary: Cookie` is not stripped for those responses and shared/browser 
caching is degraded. Update the middleware asset-path regex to support nested 
paths so the new route behavior remains cache-consistent.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40084&comment_hash=d001a4de05d084c0e7593c75f6cb37d15534d94b2481e9575d5c15812af62869&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40084&comment_hash=d001a4de05d084c0e7593c75f6cb37d15534d94b2481e9575d5c15812af62869&reaction=dislike'>👎</a>



##########
superset/extensions/local_extensions_watcher.py:
##########
@@ -29,37 +29,183 @@
 
 logger = logging.getLogger(__name__)
 
+# Sentinel file Flask watches via --extra-files.  Touching it on a real change
+# triggers a server reload without depending on cwd or the location of any
+# Python source file.
+RELOAD_TRIGGER: Path = Path(__file__).resolve().parent / ".reload_trigger"
+
 # Guard to prevent multiple initializations
 _watcher_initialized = False
 _watcher_lock = threading.Lock()
 
 
-def _get_file_handler_class() -> Any:
+def _get_file_handler_class() -> Any:  # noqa: C901
     """Get the file handler class, importing watchdog only when needed."""
     try:
-        from watchdog.events import FileSystemEventHandler
+        import hashlib
+
+        from watchdog.events import (
+            FileCreatedEvent,
+            FileModifiedEvent,
+            FileMovedEvent,
+            FileSystemEventHandler,
+        )
 
         class LocalExtensionFileHandler(FileSystemEventHandler):
-            """Custom file system event handler for LOCAL_EXTENSIONS 
directories."""
+            """Custom file system event handler for LOCAL_EXTENSIONS 
directories.
+
+            Only reacts to genuine content changes (create / modify / move) in 
the
+            dist directory, verified by comparing a SHA-256 of the file's 
content.
+            This avoids the Docker VirtioFS / osxfs problem where reading a 
file
+            generates inotify events that watchdog surfaces as modifications.
+            """
+
+            def __init__(self) -> None:
+                super().__init__()
+                # sha256 of last-seen content, keyed by absolute path. 
Populated
+                # from existing files in watched `dist` dirs at startup (see
+                # `prime_baseline`) so that startup-noise inotify events from
+                # Docker VirtioFS reads don't get treated as the first real 
edit.
+                self._file_hashes: dict[str, str] = {}
+                self._lock = threading.Lock()
+                # Trailing debounce: schedule a single reload after a quiet
+                # window so simultaneous webpack writes coalesce into one
+                # restart that fires *after* the build settles.
+                self._debounce_seconds: float = 1.0
+                self._pending_timer: threading.Timer | None = None
+                # Monotonically increasing token identifying the most recently
+                # scheduled timer. Guards the timer-already-fired race where
+                # `Timer.cancel()` can't stop a callback that has begun 
running.
+                self._reload_generation: int = 0
+
+            # ── helpers ──────────────────────────────────────────────────────
+
+            @staticmethod
+            def _sha256(path: str) -> str | None:
+                try:
+                    with open(path, "rb") as fh:
+                        return hashlib.sha256(fh.read()).hexdigest()
+                except OSError:
+                    return None
+
+            def prime_baseline(self, watch_dirs: set[str]) -> None:
+                """Pre-populate content hashes for existing files in watched
+                `dist` directories. Called once at watcher startup so a
+                developer's first real edit registers as a content change
+                rather than as the file's 'first observation'."""
+                for root_dir in watch_dirs:
+                    root = Path(root_dir)
+                    for path in root.rglob("*"):
+                        if not path.is_file():
+                            continue
+                        if "dist" not in path.parts:
+                            continue
+                        digest = self._sha256(str(path))
+                        if digest is not None:
+                            self._file_hashes[str(path)] = digest
+
+            def _content_changed(self, path: str) -> bool:
+                """Return True when the file's content differs from last seen.
+
+                With `prime_baseline` called at startup, the baseline reflects
+                what was on disk when the watcher started. A first observation
+                that differs (or doesn't exist in baseline) is treated as a
+                genuine change.
+                """
+                digest = self._sha256(path)
+                if digest is None:
+                    return False
+                old_digest = self._file_hashes.get(path)
+                self._file_hashes[path] = digest

Review Comment:
   **Suggestion:** The in-memory hash index grows without bound because every 
newly observed build artifact path is inserted and never evicted, which will 
steadily increase memory usage during long dev sessions with hashed chunk 
filenames. Remove entries on delete/move-out events (or periodically prune 
non-existent paths) to avoid unbounded state growth. [memory leak]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ LOCAL_EXTENSIONS watcher retains hashes for deleted artifacts.
   - ⚠️ Long-running dev sessions see increasing Python process memory.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Start Superset in debug mode via `create_app()` in 
`superset/app.py:23-87`; when
   `app.debug` is true, `start_local_extensions_watcher_thread(app)` is invoked 
at lines
   83-85, which calls `setup_local_extensions_watcher()` in
   `superset/extensions/local_extensions_watcher.py:216-323`.
   
   2. In `setup_local_extensions_watcher()`, configure `LOCAL_EXTENSIONS` so at 
least one
   extension directory is watched; `handler_class = _get_file_handler_class()` 
(lines
   238-241) returns `LocalExtensionFileHandler`, which allocates 
`self._file_hashes:
   dict[str, str] = {}` in its `__init__` at line 69.
   
   3. When the watcher thread starts, it instantiates `event_handler = 
handler_class()` and
   calls `event_handler.prime_baseline(watch_dirs)` at
   `superset/extensions/local_extensions_watcher.py:291-295`, then begins 
observing file
   events; for each new or modified asset under `dist`, `on_any_event()` (lines 
160-208)
   calls `_content_changed(target)` at lines 107-121.
   
   4. `_content_changed()` computes a digest and unconditionally assigns
   `self._file_hashes[path] = digest` at line 119; because there is no code 
anywhere in
   `local_extensions_watcher.py` that deletes keys from `_file_hashes`, repeated
   webpack-style rebuilds that emit new hashed chunk filenames (as implied by 
the comment
   “Chunk filenames include a content hash” in 
`superset/extensions/api.py:229`) steadily
   accumulate entries for old paths, causing `_file_hashes` to grow without 
bound over long
   dev sessions.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=917f235abec5473d86396e96b08252c3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=917f235abec5473d86396e96b08252c3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/extensions/local_extensions_watcher.py
   **Line:** 69:119
   **Comment:**
        *Memory Leak: The in-memory hash index grows without bound because 
every newly observed build artifact path is inserted and never evicted, which 
will steadily increase memory usage during long dev sessions with hashed chunk 
filenames. Remove entries on delete/move-out events (or periodically prune 
non-existent paths) to avoid unbounded state growth.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40084&comment_hash=87a6545afac74bd72a4dd9b1e74339dd8d26721f0ce5063bf7e3d052107f56c1&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40084&comment_hash=87a6545afac74bd72a4dd9b1e74339dd8d26721f0ce5063bf7e3d052107f56c1&reaction=dislike'>👎</a>



-- 
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