From 46676e73e23d4cb6b07bfe498fb2f6b814e31952 Mon Sep 17 00:00:00 2001 From: Varixo Date: Sun, 2 Nov 2025 16:39:55 +0100 Subject: [PATCH 1/3] fix: ssg on windows 2nd attempt --- .github/workflows/ci.yml | 4 +- e2e/adapters-e2e/playwright.config.ts | 1 + packages/qwik-city/src/static/main-thread.ts | 5 +- .../qwik-city/src/static/node/node-main.ts | 69 ++++++++++--------- .../qwik-city/src/static/node/node-worker.ts | 3 + .../qwik-city/src/static/worker-thread.ts | 17 ----- 6 files changed, 48 insertions(+), 51 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3c37650ea2..4bf1a1b5602 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -684,8 +684,8 @@ jobs: # browser: firefox - host: macos-latest browser: webkit - # - host: windows-latest - # browser: chromium + - host: windows-latest + browser: chromium runs-on: ${{ matrix.settings.host }} diff --git a/e2e/adapters-e2e/playwright.config.ts b/e2e/adapters-e2e/playwright.config.ts index cf7e0e59a69..81e9fbb2a19 100644 --- a/e2e/adapters-e2e/playwright.config.ts +++ b/e2e/adapters-e2e/playwright.config.ts @@ -44,5 +44,6 @@ export default defineConfig({ port: 3000, stdout: 'pipe', reuseExistingServer: !process.env.CI, + timeout: 120000, }, }); diff --git a/packages/qwik-city/src/static/main-thread.ts b/packages/qwik-city/src/static/main-thread.ts index be77f87b928..82dcd176f04 100644 --- a/packages/qwik-city/src/static/main-thread.ts +++ b/packages/qwik-city/src/static/main-thread.ts @@ -216,7 +216,10 @@ export async function mainThread(sys: System) { flushQueue(); }; - loadStaticRoutes(); + loadStaticRoutes().catch((e) => { + console.error('SSG route loading failed', e); + reject(e); + }); } catch (e) { reject(e); } diff --git a/packages/qwik-city/src/static/node/node-main.ts b/packages/qwik-city/src/static/node/node-main.ts index 4bf079ba0b6..46745f109a3 100644 --- a/packages/qwik-city/src/static/node/node-main.ts +++ b/packages/qwik-city/src/static/node/node-main.ts @@ -10,10 +10,9 @@ import type { import fs from 'node:fs'; import { cpus as nodeCpus } from 'node:os'; import { Worker } from 'node:worker_threads'; -import { isAbsolute, resolve } from 'node:path'; +import { dirname, extname, isAbsolute, join, resolve } from 'node:path'; import { ensureDir } from './node-system'; import { normalizePath } from '../../utils/fs'; -import { createSingleThreadWorker } from '../worker-thread'; export async function createNodeMainProcess(sys: System, opts: StaticGenerateOptions) { const ssgWorkers: StaticGeneratorWorker[] = []; @@ -50,38 +49,29 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt sitemapOutFile = resolve(outDir, sitemapOutFile); } } - - const singleThreadWorker = await createSingleThreadWorker(sys); - - const createWorker = (workerIndex: number) => { - if (workerIndex === 0) { - // same thread worker, don't start a new process - const ssgSameThreadWorker: StaticGeneratorWorker = { - activeTasks: 0, - totalTasks: 0, - - render: async (staticRoute) => { - ssgSameThreadWorker.activeTasks++; - ssgSameThreadWorker.totalTasks++; - const result = await singleThreadWorker(staticRoute); - ssgSameThreadWorker.activeTasks--; - return result; - }, - - terminate: async () => {}, - }; - return ssgSameThreadWorker; - } - + const createWorker = () => { let terminateResolve: (() => void) | null = null; const mainTasks = new Map(); let workerFilePath: string | URL; + let terminateTimeout: number | null = null; + // Launch the worker using the package's index module, which bootstraps the worker thread. if (typeof __filename === 'string') { - workerFilePath = __filename; + // CommonJS path + const ext = extname(__filename) || '.js'; + workerFilePath = join(dirname(__filename), `index${ext}`); } else { - workerFilePath = import.meta.url; + // ESM path (import.meta.url) + const thisUrl = new URL(import.meta.url); + const pathname = thisUrl.pathname || ''; + let ext = '.js'; + if (pathname.endsWith('.ts')) { + ext = '.ts'; + } else if (pathname.endsWith('.mjs')) { + ext = '.mjs'; + } + workerFilePath = new URL(`./index${ext}`, thisUrl); } if (typeof workerFilePath === 'string' && workerFilePath.startsWith('file://')) { @@ -89,6 +79,7 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt } const nodeWorker = new Worker(workerFilePath, { workerData: opts }); + nodeWorker.unref(); const ssgWorker: StaticGeneratorWorker = { activeTasks: 0, @@ -116,7 +107,9 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt terminateResolve = resolve; nodeWorker.postMessage(msg); }); - await nodeWorker.terminate(); + terminateTimeout = setTimeout(async () => { + await nodeWorker.terminate(); + }, 1000) as unknown as number; }, }; @@ -146,7 +139,11 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt }); nodeWorker.on('exit', (code) => { - if (code !== 1) { + if (terminateTimeout) { + clearTimeout(terminateTimeout); + terminateTimeout = null; + } + if (code !== 0) { console.error(`worker exit ${code}`); } }); @@ -200,9 +197,15 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt console.error(e); } } - ssgWorkers.length = 0; await Promise.all(promises); + ssgWorkers.length = 0; + + // On Windows, give extra time for all workers to fully exit + // This prevents resource conflicts in back-to-back builds + if (process.platform === 'win32') { + await new Promise((resolve) => setTimeout(resolve, 300)); + } }; if (sitemapOutFile) { @@ -214,7 +217,11 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt } for (let i = 0; i < maxWorkers; i++) { - ssgWorkers.push(createWorker(i)); + ssgWorkers.push(createWorker()); + // On Windows, add delay between worker creation to avoid resource contention + if (process.platform === 'win32' && i < maxWorkers - 1) { + await new Promise((resolve) => setTimeout(resolve, 100)); + } } const mainCtx: MainContext = { diff --git a/packages/qwik-city/src/static/node/node-worker.ts b/packages/qwik-city/src/static/node/node-worker.ts index 904486e684d..a2701143a77 100644 --- a/packages/qwik-city/src/static/node/node-worker.ts +++ b/packages/qwik-city/src/static/node/node-worker.ts @@ -6,5 +6,8 @@ export async function createNodeWorkerProcess( ) { parentPort?.on('message', async (msg: WorkerInputMessage) => { parentPort?.postMessage(await onMessage(msg)); + if (msg.type === 'close') { + parentPort?.close(); + } }); } diff --git a/packages/qwik-city/src/static/worker-thread.ts b/packages/qwik-city/src/static/worker-thread.ts index 962631cb683..ffbd366adb0 100644 --- a/packages/qwik-city/src/static/worker-thread.ts +++ b/packages/qwik-city/src/static/worker-thread.ts @@ -39,23 +39,6 @@ export async function workerThread(sys: System) { }); } -export async function createSingleThreadWorker(sys: System) { - const ssgOpts = sys.getOptions(); - const pendingPromises = new Set>(); - - const opts: StaticGenerateHandlerOptions = { - ...ssgOpts, - render: (await import(pathToFileURL(ssgOpts.renderModulePath).href)).default, - qwikCityPlan: (await import(pathToFileURL(ssgOpts.qwikCityPlanModulePath).href)).default, - }; - - return (staticRoute: StaticRoute) => { - return new Promise((resolve) => { - workerRender(sys, opts, staticRoute, pendingPromises, resolve); - }); - }; -} - async function workerRender( sys: System, opts: StaticGenerateHandlerOptions, From de9393add50452a249dd238547ecf75c3bd3d466 Mon Sep 17 00:00:00 2001 From: Varixo Date: Sun, 2 Nov 2025 17:09:56 +0100 Subject: [PATCH 2/3] fix(e2e): increase timeout for CI --- e2e/adapters-e2e/playwright.config.ts | 4 ++-- e2e/qwik-cli-e2e/utils/index.ts | 2 +- e2e/qwik-react-e2e/playwright.config.ts | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/e2e/adapters-e2e/playwright.config.ts b/e2e/adapters-e2e/playwright.config.ts index 81e9fbb2a19..1483b6432c8 100644 --- a/e2e/adapters-e2e/playwright.config.ts +++ b/e2e/adapters-e2e/playwright.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ }, // Increase global timeout for service worker tests - timeout: 30000, + timeout: process.env.CI ? 120000 : 30000, projects: [ { @@ -44,6 +44,6 @@ export default defineConfig({ port: 3000, stdout: 'pipe', reuseExistingServer: !process.env.CI, - timeout: 120000, + timeout: process.env.CI ? 120000 : 30000, }, }); diff --git a/e2e/qwik-cli-e2e/utils/index.ts b/e2e/qwik-cli-e2e/utils/index.ts index 72db5824710..b8328e4cf67 100644 --- a/e2e/qwik-cli-e2e/utils/index.ts +++ b/e2e/qwik-cli-e2e/utils/index.ts @@ -205,4 +205,4 @@ export function log(text: string) { console.log(yellow('E2E: ' + text)); } -export const DEFAULT_TIMEOUT = 30000; +export const DEFAULT_TIMEOUT = process.env.CI ? 120000 : 30000; diff --git a/e2e/qwik-react-e2e/playwright.config.ts b/e2e/qwik-react-e2e/playwright.config.ts index cf7e0e59a69..1483b6432c8 100644 --- a/e2e/qwik-react-e2e/playwright.config.ts +++ b/e2e/qwik-react-e2e/playwright.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ }, // Increase global timeout for service worker tests - timeout: 30000, + timeout: process.env.CI ? 120000 : 30000, projects: [ { @@ -44,5 +44,6 @@ export default defineConfig({ port: 3000, stdout: 'pipe', reuseExistingServer: !process.env.CI, + timeout: process.env.CI ? 120000 : 30000, }, }); From c60f44ae6b32f3a732d5f11b65dd401aef777977 Mon Sep 17 00:00:00 2001 From: Varixo Date: Sun, 2 Nov 2025 18:20:15 +0100 Subject: [PATCH 3/3] fix: docs build --- packages/qwik-city/src/static/node/node-main.ts | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/packages/qwik-city/src/static/node/node-main.ts b/packages/qwik-city/src/static/node/node-main.ts index 46745f109a3..6c4fcead575 100644 --- a/packages/qwik-city/src/static/node/node-main.ts +++ b/packages/qwik-city/src/static/node/node-main.ts @@ -10,7 +10,7 @@ import type { import fs from 'node:fs'; import { cpus as nodeCpus } from 'node:os'; import { Worker } from 'node:worker_threads'; -import { dirname, extname, isAbsolute, join, resolve } from 'node:path'; +import { isAbsolute, resolve } from 'node:path'; import { ensureDir } from './node-system'; import { normalizePath } from '../../utils/fs'; @@ -58,20 +58,9 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt // Launch the worker using the package's index module, which bootstraps the worker thread. if (typeof __filename === 'string') { - // CommonJS path - const ext = extname(__filename) || '.js'; - workerFilePath = join(dirname(__filename), `index${ext}`); + workerFilePath = __filename; } else { - // ESM path (import.meta.url) - const thisUrl = new URL(import.meta.url); - const pathname = thisUrl.pathname || ''; - let ext = '.js'; - if (pathname.endsWith('.ts')) { - ext = '.ts'; - } else if (pathname.endsWith('.mjs')) { - ext = '.mjs'; - } - workerFilePath = new URL(`./index${ext}`, thisUrl); + workerFilePath = import.meta.url; } if (typeof workerFilePath === 'string' && workerFilePath.startsWith('file://')) {