mikebridge opened a new pull request, #45364:
URL: https://github.com/apache/superset/pull/45364

   ### SUMMARY
   
   **TL;DR:** the `Python-Unit` job dies at 99% on roughly one run in ten since 
October 12 because every unit-test module leaks a full Superset app. Each test 
module builds its own `SupersetApp` through the module-scoped `app` fixture, 
and two process-wide singletons kept every app ever built reachable, so each 
pytest-xdist worker's resident memory grew by about 10 MB per module and 
reached 5 to 7 GB per worker on the 857-module suite. Two workers plus the 
controller no longer fit on the hosted runner; the VM stops responding and the 
job sits until its 45-minute timeout. Fixes #45362.
   
   **The two retention roots**
   
   1. `SupersetAppInitializer.configure_celery` subclassed whatever 
`celery_app.Task` currently was and installed the subclass. On a second 
initialization in the same process that was the previous `AppContextTask`, so 
each wrapper wrapped the last one and every app ever initialized stayed 
reachable through the task class hierarchy (after 8 test modules the MRO 
carried 9 wrappers). The wrapper now carries a marker and a reference to the 
base it wrapped; a re-initialization unwraps one level and subclasses the 
original Celery base again. A single initialization, which is every production 
path, finds no marker and constructs exactly what it did before.
   2. `init_views` adds a set of menu items to the Flask-AppBuilder singleton 
on every initialization, and each item's link closure holds the app that 
registered it. The unit fixture only reset `appbuilder.baseviews`; it now 
resets `appbuilder.menu` alongside.
   
   **Fixture design.** The fixture stays module-scoped. With both roots fixed, 
each module's app is collectable on its own (measured: 8 apps initialized, 1 
alive at the end), so changing the scope was unnecessary. A session-scoped app 
would also have removed the growth, but it would share one app across the 
suite's several hundred in-place `app.config[...]` mutations and the indirect 
`app` parametrizations, a much larger blast radius for the same result. The 
fixture body moved into a module-level `build_app()` helper so the regression 
test and the fixture initialize apps the same way.
   
   **Regression test.** `tests/unit_tests/test_app_reinitialization.py` 
initializes twice and asserts that `celery_app.Task.__mro__` does not grow (and 
carries exactly one wrapper) and that the first app is dead through a weakref 
after `gc.collect()`, naming the referrer types if not. Both tests fail on 
`master` (`assert 7 == 6`; `assert <SupersetApp ...> is None`) and pass here.
   
   **Evidence**
   
   Lost-runner kills of the unit-tests job (job lingered 15 minutes or more 
after the step ended; GitHub annotation "The job has exceeded the maximum 
execution time of 45m0s"), from the Actions API: zero in 278 cancelled runs 
between the xdist switch (#44178, 2026-09-12) and 2026-10-11; then 1, 4, 23 and 
22 on October 12 through 15, against about 500 successes, including `master` 
itself. Sample killed runs: 
https://github.com/apache/superset/actions/runs/38619149658, 
https://github.com/apache/superset/actions/runs/38752136752 (both attempts), 
and `master` https://github.com/apache/superset/actions/runs/38563726398. The 
suite grew from 21,457 to 27,507 items between October 3 and October 15; the 
retained cost is per module, so growth alone crossed the ceiling.
   
   Full suite with the exact CI command at `-n 2 --dist loadfile`, per-worker 
resident memory (local macOS; absolute numbers differ from Linux, the 
before/after ratio is the point):
   
   | Tree | Worker | Peak RSS | Max RSS | Result |
   |---|---|---|---|---|
   | master e0e728ef | gw0 | 5,068 MB | 5,548 MB | 28 failed (local baseline), 
27,433 passed |
   | master e0e728ef | gw1 | 5,467 MB | 6,348 MB | |
   | this branch | gw0 | 3,130 MB | 3,151 MB | 22 failed (strict subset of the 
baseline), 27,441 passed |
   | this branch | gw1 | 3,634 MB | 3,642 MB | |
   
   The branch's failures are a strict subset of master's local baseline (the 
six extra on master are `check_pot_drift_test.py`, which cannot run from the 
archive export used for the baseline). A residual climb of about 1 to 2 MB per 
module remains, down from 10 MB, and is left for a follow-up.
   
   **Reviews.** python-review and preset-review lenses (0 blocking; all LOW and 
the one MEDIUM, which asked for the full-suite numbers above, addressed before 
the first commit), plus an independent Codex review that approved the behaviour 
and asked for the annotation and docstring additions in the second commit.
   
   This PR and its description were generated by Claude (AI) on behalf of 
@mikebridge, who directed the investigation and reviewed the result.
   
   ### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
   
   Not applicable.
   
   ### TESTING INSTRUCTIONS
   
   1. On `master`, run `pytest tests/unit_tests/test_app_reinitialization.py` 
with this branch's test file and conftest only: both tests fail.
   2. On this branch, the same command passes.
   3. Full suite: `pytest -n 2 --dist loadfile --cov=superset ./tests/common 
./tests/unit_tests` while sampling each worker's RSS (for example `ps -o rss` 
on the `gw*` processes, or a small `pytest_runtest_protocol` hookwrapper 
reading `psutil.Process().memory_info().rss`): peaks drop from 5 to 7 GB per 
worker to about 3 to 3.6 GB, and the per-module growth drops from about 10 MB 
to 1 to 2 MB.
   
   ### ADDITIONAL INFORMATION
   
   - [x] Has associated issue: Fixes #45362
   - [ ] Required feature flags:
   - [ ] Changes UI
   - [ ] Includes DB Migration (follow approval process in 
[SIP-59](https://github.com/apache/superset/issues/13351))
     - [ ] Migration is atomic, supports rollback & is backwards-compatible
     - [ ] Confirm DB migration upgrade and downgrade tested
     - [ ] Runtime estimates and downtime expectations provided
   - [ ] Introduces new feature or API
   - [ ] Removes existing feature or API
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


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