From b0bc04a4761230207b70a32161c6650a55b15947 Mon Sep 17 00:00:00 2001 From: Joe Li Date: Fri, 31 Jul 2026 15:09:16 -0700 Subject: [PATCH 1/2] ci(e2e): stop recycling the single gunicorn worker mid-run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The E2E backend runs with `--workers 1` (deliberately — multi-worker exposes races in the SQL Lab to Explore flow) alongside `--max-requests 500 --max-requests-jitter 50`. With one worker there is nothing to roll over to, so every recycle takes the whole backend offline: gunicorn drains for the full 30s graceful timeout because browser keep-alive connections stay open, then the replacement worker spends ~5s booting Superset. A Playwright run issues roughly 3800 requests over ~8 minutes, so the cap fired seven times, producing seven ~35s total outages. Specs that happened to navigate during one timed out waiting for the page to render and were rescued by retries — visible as "flaky" rather than as failures. Cypress runs hit the same windows, absorbed by `cypress_run.py --retries 5`. Lowering `--graceful-timeout` is not sufficient: even at 5s the gap exceeds the 6-10s a dashboard load plus chart render needs. Removing the recycle removes the outage, and there is no leak evidence to justify keeping it for an 8-minute process. Co-Authored-By: Claude Opus 5 --- .github/workflows/bashlib.sh | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/bashlib.sh b/.github/workflows/bashlib.sh index d36369f4353c..93a62185d57d 100644 --- a/.github/workflows/bashlib.sh +++ b/.github/workflows/bashlib.sh @@ -201,18 +201,23 @@ cypress-run-all() { # navigation flow under E2E. We diverge from the entrypoint on: # --timeout 120: heavy dashboard import/export specs exceed the 60s # default - # --max-requests / --max-requests-jitter: recycle the worker under - # test load to avoid leaks accumulating across the run # superset.app:create_app(): explicit factory so we don't depend on # FLASK_APP being exported + # + # No --max-requests, matching the entrypoint's default of 0 (recycling + # off). With a single worker a recycle takes the whole backend offline for + # the graceful-timeout drain — browser keep-alive connections hold it open + # for the full 30s — plus ~5s of app boot. A run issues ~3800 requests in + # ~8 minutes, so recycling every 500 produced seven ~35s outages per run + # and flaked whichever specs happened to navigate into one. Lowering + # --graceful-timeout is not enough: a dashboard load plus chart render + # needs 6-10s, which still lands inside the window. nohup gunicorn \ --bind "127.0.0.1:$port" \ --workers 1 \ --worker-class gthread \ --threads 20 \ --timeout 120 \ - --max-requests 500 \ - --max-requests-jitter 50 \ --access-logfile - \ --error-logfile - \ "superset.app:create_app()" \ @@ -294,16 +299,14 @@ playwright-run() { export PLAYWRIGHT_BASE_URL # See cypress-run-all() above for the args rationale (1 worker × 20 - # gthread threads matching docker/entrypoints/run-server.sh, plus a - # 120s timeout and request-recycling for heavy E2E load). + # gthread threads matching docker/entrypoints/run-server.sh, a 120s + # timeout for heavy E2E load, and why worker recycling is off). nohup gunicorn \ --bind "127.0.0.1:$port" \ --workers 1 \ --worker-class gthread \ --threads 20 \ --timeout 120 \ - --max-requests 500 \ - --max-requests-jitter 50 \ --access-logfile - \ --error-logfile - \ "superset.app:create_app()" \ From 7e772fd6188fbd0b39f590b0291213e33bea1696 Mon Sep 17 00:00:00 2001 From: Joe Li Date: Fri, 31 Jul 2026 15:09:24 -0700 Subject: [PATCH 2/2] test(dashboard): give the mixed-chart filter spec the slow-test budget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every other dashboard spec that builds its fixtures over the API — the clear-all-filters, gauge-interval, and dashboard-controls specs — raises its budget to TIMEOUT.SLOW_TEST. This one did not, so it ran on the 30s config default despite doing four API round-trips before a full dashboard load with a preselected native filter. Co-Authored-By: Claude Opus 5 --- .../tests/dashboard/mixed-chart-dashboard-filters.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/superset-frontend/playwright/tests/dashboard/mixed-chart-dashboard-filters.spec.ts b/superset-frontend/playwright/tests/dashboard/mixed-chart-dashboard-filters.spec.ts index f912b7581dbb..0219b8ffd9d7 100644 --- a/superset-frontend/playwright/tests/dashboard/mixed-chart-dashboard-filters.spec.ts +++ b/superset-frontend/playwright/tests/dashboard/mixed-chart-dashboard-filters.spec.ts @@ -42,6 +42,7 @@ import { import { getDatasetByName } from '../../helpers/api/dataset'; import { extractIdFromResponse } from '../../helpers/api/assertions'; import { DashboardPage } from '../../pages/DashboardPage'; +import { TIMEOUT } from '../../utils/constants'; import { buildFilterJsonMetadata, buildSelectFilter, @@ -54,6 +55,11 @@ const FILTER_VALUE = 'boy'; testWithAssets( 'Mixed chart applies dashboard filter to both queries (#29519)', async ({ page, testAssets }) => { + // Four API round-trips of setup precede a full dashboard load with a + // preselected native filter, matching the other dashboard specs that build + // their fixtures over the API rather than importing them. + testWithAssets.setTimeout(TIMEOUT.SLOW_TEST); + const dataset = await getDatasetByName(page, DATASET_NAME); if (!dataset) { throw new Error(`Dataset ${DATASET_NAME} not found`);