Skip to content

Commit 46676e7

Browse files
committed
fix: ssg on windows 2nd attempt
1 parent f263433 commit 46676e7

File tree

6 files changed

+48
-51
lines changed

6 files changed

+48
-51
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,8 +684,8 @@ jobs:
684684
# browser: firefox
685685
- host: macos-latest
686686
browser: webkit
687-
# - host: windows-latest
688-
# browser: chromium
687+
- host: windows-latest
688+
browser: chromium
689689

690690
runs-on: ${{ matrix.settings.host }}
691691

e2e/adapters-e2e/playwright.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,6 @@ export default defineConfig({
4444
port: 3000,
4545
stdout: 'pipe',
4646
reuseExistingServer: !process.env.CI,
47+
timeout: 120000,
4748
},
4849
});

packages/qwik-city/src/static/main-thread.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,10 @@ export async function mainThread(sys: System) {
216216
flushQueue();
217217
};
218218

219-
loadStaticRoutes();
219+
loadStaticRoutes().catch((e) => {
220+
console.error('SSG route loading failed', e);
221+
reject(e);
222+
});
220223
} catch (e) {
221224
reject(e);
222225
}

packages/qwik-city/src/static/node/node-main.ts

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ import type {
1010
import fs from 'node:fs';
1111
import { cpus as nodeCpus } from 'node:os';
1212
import { Worker } from 'node:worker_threads';
13-
import { isAbsolute, resolve } from 'node:path';
13+
import { dirname, extname, isAbsolute, join, resolve } from 'node:path';
1414
import { ensureDir } from './node-system';
1515
import { normalizePath } from '../../utils/fs';
16-
import { createSingleThreadWorker } from '../worker-thread';
1716

1817
export async function createNodeMainProcess(sys: System, opts: StaticGenerateOptions) {
1918
const ssgWorkers: StaticGeneratorWorker[] = [];
@@ -50,45 +49,37 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt
5049
sitemapOutFile = resolve(outDir, sitemapOutFile);
5150
}
5251
}
53-
54-
const singleThreadWorker = await createSingleThreadWorker(sys);
55-
56-
const createWorker = (workerIndex: number) => {
57-
if (workerIndex === 0) {
58-
// same thread worker, don't start a new process
59-
const ssgSameThreadWorker: StaticGeneratorWorker = {
60-
activeTasks: 0,
61-
totalTasks: 0,
62-
63-
render: async (staticRoute) => {
64-
ssgSameThreadWorker.activeTasks++;
65-
ssgSameThreadWorker.totalTasks++;
66-
const result = await singleThreadWorker(staticRoute);
67-
ssgSameThreadWorker.activeTasks--;
68-
return result;
69-
},
70-
71-
terminate: async () => {},
72-
};
73-
return ssgSameThreadWorker;
74-
}
75-
52+
const createWorker = () => {
7653
let terminateResolve: (() => void) | null = null;
7754
const mainTasks = new Map<string, WorkerMainTask>();
7855

7956
let workerFilePath: string | URL;
57+
let terminateTimeout: number | null = null;
8058

59+
// Launch the worker using the package's index module, which bootstraps the worker thread.
8160
if (typeof __filename === 'string') {
82-
workerFilePath = __filename;
61+
// CommonJS path
62+
const ext = extname(__filename) || '.js';
63+
workerFilePath = join(dirname(__filename), `index${ext}`);
8364
} else {
84-
workerFilePath = import.meta.url;
65+
// ESM path (import.meta.url)
66+
const thisUrl = new URL(import.meta.url);
67+
const pathname = thisUrl.pathname || '';
68+
let ext = '.js';
69+
if (pathname.endsWith('.ts')) {
70+
ext = '.ts';
71+
} else if (pathname.endsWith('.mjs')) {
72+
ext = '.mjs';
73+
}
74+
workerFilePath = new URL(`./index${ext}`, thisUrl);
8575
}
8676

8777
if (typeof workerFilePath === 'string' && workerFilePath.startsWith('file://')) {
8878
workerFilePath = new URL(workerFilePath);
8979
}
9080

9181
const nodeWorker = new Worker(workerFilePath, { workerData: opts });
82+
nodeWorker.unref();
9283

9384
const ssgWorker: StaticGeneratorWorker = {
9485
activeTasks: 0,
@@ -116,7 +107,9 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt
116107
terminateResolve = resolve;
117108
nodeWorker.postMessage(msg);
118109
});
119-
await nodeWorker.terminate();
110+
terminateTimeout = setTimeout(async () => {
111+
await nodeWorker.terminate();
112+
}, 1000) as unknown as number;
120113
},
121114
};
122115

@@ -146,7 +139,11 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt
146139
});
147140

148141
nodeWorker.on('exit', (code) => {
149-
if (code !== 1) {
142+
if (terminateTimeout) {
143+
clearTimeout(terminateTimeout);
144+
terminateTimeout = null;
145+
}
146+
if (code !== 0) {
150147
console.error(`worker exit ${code}`);
151148
}
152149
});
@@ -200,9 +197,15 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt
200197
console.error(e);
201198
}
202199
}
203-
ssgWorkers.length = 0;
204200

205201
await Promise.all(promises);
202+
ssgWorkers.length = 0;
203+
204+
// On Windows, give extra time for all workers to fully exit
205+
// This prevents resource conflicts in back-to-back builds
206+
if (process.platform === 'win32') {
207+
await new Promise((resolve) => setTimeout(resolve, 300));
208+
}
206209
};
207210

208211
if (sitemapOutFile) {
@@ -214,7 +217,11 @@ export async function createNodeMainProcess(sys: System, opts: StaticGenerateOpt
214217
}
215218

216219
for (let i = 0; i < maxWorkers; i++) {
217-
ssgWorkers.push(createWorker(i));
220+
ssgWorkers.push(createWorker());
221+
// On Windows, add delay between worker creation to avoid resource contention
222+
if (process.platform === 'win32' && i < maxWorkers - 1) {
223+
await new Promise((resolve) => setTimeout(resolve, 100));
224+
}
218225
}
219226

220227
const mainCtx: MainContext = {

packages/qwik-city/src/static/node/node-worker.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@ export async function createNodeWorkerProcess(
66
) {
77
parentPort?.on('message', async (msg: WorkerInputMessage) => {
88
parentPort?.postMessage(await onMessage(msg));
9+
if (msg.type === 'close') {
10+
parentPort?.close();
11+
}
912
});
1013
}

packages/qwik-city/src/static/worker-thread.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,6 @@ export async function workerThread(sys: System) {
3939
});
4040
}
4141

42-
export async function createSingleThreadWorker(sys: System) {
43-
const ssgOpts = sys.getOptions();
44-
const pendingPromises = new Set<Promise<any>>();
45-
46-
const opts: StaticGenerateHandlerOptions = {
47-
...ssgOpts,
48-
render: (await import(pathToFileURL(ssgOpts.renderModulePath).href)).default,
49-
qwikCityPlan: (await import(pathToFileURL(ssgOpts.qwikCityPlanModulePath).href)).default,
50-
};
51-
52-
return (staticRoute: StaticRoute) => {
53-
return new Promise<StaticWorkerRenderResult>((resolve) => {
54-
workerRender(sys, opts, staticRoute, pendingPromises, resolve);
55-
});
56-
};
57-
}
58-
5942
async function workerRender(
6043
sys: System,
6144
opts: StaticGenerateHandlerOptions,

0 commit comments

Comments
 (0)