quickwin 1.1.4 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,35 +1,80 @@
1
1
  import { createServer } from '../lib/http-server.js'
2
- import * as std from 'std'
3
-
4
- function readAll(file: std.FILE): string {
5
- const chunks: Uint8Array[] = []
6
- const buf = new Uint8Array(4096)
7
- while (true) {
8
- const n = file.read(buf.buffer, 0, 4096)
9
- if (n <= 0) break
10
- chunks.push(buf.slice(0, n))
11
- }
12
- let total = 0
13
- for (const c of chunks) total += c.length
14
- const out = new Uint8Array(total)
15
- let offset = 0
16
- for (const c of chunks) { out.set(c, offset); offset += c.length }
17
- return new TextDecoder().decode(out)
2
+ import * as os from 'os'
3
+
4
+ // WORKER_DATA_URL 由 esbuild --define 在 build.ts 中注入(base64 data: URL)
5
+ declare const WORKER_DATA_URL: string
6
+
7
+ const EXEC_TIMEOUT_MS = 60_000
8
+
9
+ interface ExecResult {
10
+ out: Uint8Array
11
+ code: number
12
+ error: string | null
13
+ }
14
+
15
+ function runInWorker(cmd: string): Promise<ExecResult> {
16
+ return new Promise((resolve, reject) => {
17
+ const worker = new os.Worker(WORKER_DATA_URL)
18
+ const id = 1
19
+ let settled = false
20
+ let timer: ReturnType<typeof os.setTimeout>
21
+
22
+ const finish = (fn: () => void) => {
23
+ if (settled) return
24
+ settled = true
25
+ os.clearTimeout(timer)
26
+ worker.onmessage = null
27
+ fn()
28
+ }
29
+
30
+ timer = os.setTimeout(() => {
31
+ finish(() => reject(new Error('exec timeout')))
32
+ }, EXEC_TIMEOUT_MS)
33
+
34
+ worker.onmessage = (e) => {
35
+ const msg = e.data as { type: string; id: number; out?: Uint8Array; code?: number; error?: string | null }
36
+ if (msg.type !== 'result' || msg.id !== id) return
37
+ finish(() => resolve({ out: msg.out ?? new Uint8Array(0), code: msg.code ?? -1, error: msg.error ?? null }))
38
+ }
39
+
40
+ try {
41
+ worker.postMessage({ type: 'run', id, cmd })
42
+ } catch (ex) {
43
+ finish(() => reject(ex instanceof Error ? ex : new Error(String(ex))))
44
+ }
45
+ })
18
46
  }
19
47
 
20
48
  const server = createServer(async (req) => {
21
- if (req.method === 'POST' && new URL(req.url).pathname === '/exec') {
49
+ const path = new URL(req.url).pathname
50
+ if (req.method === 'GET' && (path === '/' || path === '/health')) {
51
+ return new Response('ok', { headers: { 'Content-Type': 'text/plain' } })
52
+ }
53
+ if (req.method === 'POST' && path === '/exec') {
22
54
  const body = await req.json() as { cmd: string }
23
- const file = std.popen(body.cmd, 'r')
24
- if (!file) return new Response('popen failed', { status: 500 })
25
- const output = readAll(file)
26
- file.close()
27
- return new Response(output, {
28
- headers: { 'Content-Type': 'application/octet-stream' }
29
- })
55
+ try {
56
+ const { out, code, error } = await runInWorker(body.cmd)
57
+ if (error) {
58
+ return new Response(JSON.stringify({ error }), {
59
+ status: 500,
60
+ headers: { 'Content-Type': 'application/json' }
61
+ })
62
+ }
63
+ return new Response(out, {
64
+ headers: {
65
+ 'Content-Type': 'application/octet-stream',
66
+ 'X-Exit-Code': String(code),
67
+ }
68
+ })
69
+ } catch (ex) {
70
+ return new Response(JSON.stringify({ error: String(ex) }), {
71
+ status: 504,
72
+ headers: { 'Content-Type': 'application/json' }
73
+ })
74
+ }
30
75
  }
31
76
  return new Response('Not Found', { status: 404 })
32
77
  })
33
78
 
34
- server.listen(8080)
35
- console.log('listening on http://localhost:8080')
79
+ const rc = server.listen(8080, '0.0.0.0')
80
+ console.log('listen rc=' + rc + ' addr=' + JSON.stringify(server.address()))
@@ -0,0 +1,45 @@
1
+ import * as os from 'os';
2
+ import * as std from 'std';
3
+ function readBytes(file) {
4
+ const chunks = [];
5
+ const buf = new Uint8Array(4096);
6
+ while (true) {
7
+ const n = file.read(buf.buffer, 0, 4096);
8
+ if (n <= 0)
9
+ break;
10
+ chunks.push(buf.slice(0, n));
11
+ }
12
+ let total = 0;
13
+ for (const c of chunks)
14
+ total += c.length;
15
+ const out = new Uint8Array(total);
16
+ let offset = 0;
17
+ for (const c of chunks) {
18
+ out.set(c, offset);
19
+ offset += c.length;
20
+ }
21
+ return out;
22
+ }
23
+ const parent = os.Worker.parent;
24
+ parent.onmessage = (e) => {
25
+ const msg = e.data;
26
+ if (msg.type !== 'run' || typeof msg.cmd !== 'string')
27
+ return;
28
+ let out = new Uint8Array(0);
29
+ let code = -1;
30
+ let error = null;
31
+ try {
32
+ const file = std.popen(msg.cmd, 'r');
33
+ if (!file) {
34
+ error = 'popen failed';
35
+ }
36
+ else {
37
+ out = readBytes(file);
38
+ code = file.close();
39
+ }
40
+ }
41
+ catch (ex) {
42
+ error = String(ex);
43
+ }
44
+ parent.postMessage({ type: 'result', id: msg.id, out, code, error });
45
+ };
@@ -0,0 +1,39 @@
1
+ import * as os from 'os'
2
+ import * as std from 'std'
3
+
4
+ function readBytes(file: std.FILE): Uint8Array {
5
+ const chunks: Uint8Array[] = []
6
+ const buf = new Uint8Array(4096)
7
+ while (true) {
8
+ const n = file.read(buf.buffer, 0, 4096)
9
+ if (n <= 0) break
10
+ chunks.push(buf.slice(0, n))
11
+ }
12
+ let total = 0
13
+ for (const c of chunks) total += c.length
14
+ const out = new Uint8Array(total)
15
+ let offset = 0
16
+ for (const c of chunks) { out.set(c, offset); offset += c.length }
17
+ return out
18
+ }
19
+
20
+ const parent = os.Worker.parent
21
+ parent.onmessage = (e) => {
22
+ const msg = e.data as { type: string; id: number; cmd?: string }
23
+ if (msg.type !== 'run' || typeof msg.cmd !== 'string') return
24
+ let out: Uint8Array = new Uint8Array(0)
25
+ let code = -1
26
+ let error: string | null = null
27
+ try {
28
+ const file = std.popen(msg.cmd, 'r')
29
+ if (!file) {
30
+ error = 'popen failed'
31
+ } else {
32
+ out = readBytes(file)
33
+ code = file.close()
34
+ }
35
+ } catch (ex) {
36
+ error = String(ex)
37
+ }
38
+ parent.postMessage({ type: 'result', id: msg.id, out, code, error })
39
+ }
@@ -0,0 +1,104 @@
1
+ import * as std from 'std';
2
+ import * as gui from 'gui';
3
+ import * as win from 'win';
4
+ import * as ffi from 'ffi';
5
+ const user32 = win.LoadLibrary('user32.dll');
6
+ if (!user32) {
7
+ print('LoadLibrary user32 failed');
8
+ std.exit(1);
9
+ }
10
+ function loadProc(lib, name) {
11
+ const ptr = win.GetProcAddress(lib, name);
12
+ if (!ptr) {
13
+ print('GetProcAddress ' + name + ' failed');
14
+ std.exit(1);
15
+ }
16
+ return ptr;
17
+ }
18
+ const EnumDisplaySettingsA = loadProc(user32, 'EnumDisplaySettingsA');
19
+ const ChangeDisplaySettingsA = loadProc(user32, 'ChangeDisplaySettingsA');
20
+ /** DEVMODEA used by EnumDisplaySettings; driver reports actual dmSize (often 124). */
21
+ const DEVMODE_SIZE = 220;
22
+ const DM_PELSWIDTH = 0x00800000;
23
+ const DM_PELSHEIGHT = 0x01000000;
24
+ const ENUM_CURRENT_SETTINGS = -1;
25
+ const CDS_UPDATEREGISTRY = 0x00000001;
26
+ const CDS_TEST = 0x00000002;
27
+ const DISP_CHANGE = {
28
+ 0: 'SUCCESSFUL',
29
+ 1: 'RESTART',
30
+ [-1]: 'FAILED',
31
+ [-2]: 'BADMODE',
32
+ [-3]: 'NOTUPDATED',
33
+ [-4]: 'BADFLAGS',
34
+ [-5]: 'BADPARAM',
35
+ [-6]: 'BADDUALVIEW',
36
+ };
37
+ function newDevMode() {
38
+ const buf = new ArrayBuffer(DEVMODE_SIZE);
39
+ new DataView(buf).setUint16(36, DEVMODE_SIZE, true);
40
+ return buf;
41
+ }
42
+ function enumMode(modeNum, buf) {
43
+ return ffi.ffiCall(EnumDisplaySettingsA, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_SINT32, ffi.FFI_TYPE_POINTER], [null, modeNum, buf], ffi.FFI_TYPE_SINT32);
44
+ }
45
+ function getPels(dv) {
46
+ return [dv.getUint32(108, true), dv.getUint32(112, true), dv.getUint32(120, true)];
47
+ }
48
+ function applyFromCurrent(w, h, flags, label) {
49
+ const dm = newDevMode();
50
+ if (!enumMode(ENUM_CURRENT_SETTINGS, dm)) {
51
+ print(label + ': EnumDisplaySettings CURRENT failed');
52
+ return null;
53
+ }
54
+ const dv = new DataView(dm);
55
+ const before = getPels(dv);
56
+ const size = dv.getUint16(36, true);
57
+ const extra = dv.getUint16(38, true);
58
+ const fields = dv.getUint32(40, true);
59
+ const bpp = dv.getUint32(104, true);
60
+ const freq = dv.getUint32(120, true);
61
+ print(label + ': base size=' + size + ' extra=' + extra + ' fields=0x' + fields.toString(16) +
62
+ ' ' + before[0] + 'x' + before[1] + '@' + freq + ' bpp=' + bpp);
63
+ dv.setUint32(40, fields | DM_PELSWIDTH | DM_PELSHEIGHT, true);
64
+ dv.setUint32(108, w, true);
65
+ dv.setUint32(112, h, true);
66
+ const ret = ffi.ffiCall(ChangeDisplaySettingsA, [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_UINT32], [dm, flags], ffi.FFI_TYPE_SINT32);
67
+ print(label + ': ChangeDisplaySettings ' + w + 'x' + h +
68
+ ' flags=0x' + flags.toString(16) + ' ret=' + ret + ' (' + (DISP_CHANGE[ret] ?? '?') + ')');
69
+ return ret;
70
+ }
71
+ const targetW = Number(scriptArgs[1] ?? '1024');
72
+ const targetH = Number(scriptArgs[2] ?? '768');
73
+ if (!Number.isFinite(targetW) || !Number.isFinite(targetH) || targetW <= 0 || targetH <= 0) {
74
+ print('usage: qwin.exe setres.js [width] [height]');
75
+ std.exit(1);
76
+ }
77
+ print('before GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()));
78
+ const probe = newDevMode();
79
+ const modes32 = [];
80
+ for (let i = 0; i < 200; i++) {
81
+ if (!enumMode(i, probe))
82
+ break;
83
+ const dv = new DataView(probe);
84
+ if (dv.getUint32(104, true) !== 32)
85
+ continue;
86
+ const p = getPels(dv);
87
+ modes32.push(p[0] + 'x' + p[1] + '@' + p[2]);
88
+ }
89
+ print('32bpp modes sample: ' + modes32.slice(0, 25).join(', ') + (modes32.length > 25 ? ' ...' : ''));
90
+ const testRet = applyFromCurrent(targetW, targetH, CDS_TEST, 'TEST');
91
+ if (testRet === 0) {
92
+ applyFromCurrent(targetW, targetH, 0, 'DYNAMIC');
93
+ print('after dynamic GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()));
94
+ applyFromCurrent(targetW, targetH, CDS_UPDATEREGISTRY, 'REGISTRY');
95
+ print('after registry GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()));
96
+ }
97
+ else if (testRet !== null) {
98
+ print('target rejected; try 800x600');
99
+ const t2 = applyFromCurrent(800, 600, CDS_TEST, 'TEST800');
100
+ if (t2 === 0) {
101
+ applyFromCurrent(800, 600, 0, 'DYNAMIC800');
102
+ print('after 800x600 GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()));
103
+ }
104
+ }
@@ -0,0 +1,120 @@
1
+ import * as std from 'std'
2
+ import * as gui from 'gui'
3
+ import * as win from 'win'
4
+ import * as ffi from 'ffi'
5
+
6
+ const user32 = win.LoadLibrary('user32.dll')
7
+ if (!user32) { print('LoadLibrary user32 failed'); std.exit(1) }
8
+
9
+ function loadProc(lib: win.HMODULE, name: string): number {
10
+ const ptr = win.GetProcAddress(lib, name)
11
+ if (!ptr) { print('GetProcAddress ' + name + ' failed'); std.exit(1) }
12
+ return ptr
13
+ }
14
+
15
+ const EnumDisplaySettingsA = loadProc(user32, 'EnumDisplaySettingsA')
16
+ const ChangeDisplaySettingsA = loadProc(user32, 'ChangeDisplaySettingsA')
17
+
18
+ /** DEVMODEA used by EnumDisplaySettings; driver reports actual dmSize (often 124). */
19
+ const DEVMODE_SIZE = 220
20
+ const DM_PELSWIDTH = 0x00800000
21
+ const DM_PELSHEIGHT = 0x01000000
22
+ const ENUM_CURRENT_SETTINGS = -1
23
+ const CDS_UPDATEREGISTRY = 0x00000001
24
+ const CDS_TEST = 0x00000002
25
+
26
+ const DISP_CHANGE: Record<number, string> = {
27
+ 0: 'SUCCESSFUL',
28
+ 1: 'RESTART',
29
+ [-1]: 'FAILED',
30
+ [-2]: 'BADMODE',
31
+ [-3]: 'NOTUPDATED',
32
+ [-4]: 'BADFLAGS',
33
+ [-5]: 'BADPARAM',
34
+ [-6]: 'BADDUALVIEW',
35
+ }
36
+
37
+ function newDevMode(): ArrayBuffer {
38
+ const buf = new ArrayBuffer(DEVMODE_SIZE)
39
+ new DataView(buf).setUint16(36, DEVMODE_SIZE, true)
40
+ return buf
41
+ }
42
+
43
+ function enumMode(modeNum: number, buf: ArrayBuffer): number {
44
+ return ffi.ffiCall(
45
+ EnumDisplaySettingsA,
46
+ [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_SINT32, ffi.FFI_TYPE_POINTER],
47
+ [null, modeNum, buf],
48
+ ffi.FFI_TYPE_SINT32,
49
+ )
50
+ }
51
+
52
+ function getPels(dv: DataView): [number, number, number] {
53
+ return [dv.getUint32(108, true), dv.getUint32(112, true), dv.getUint32(120, true)]
54
+ }
55
+
56
+ function applyFromCurrent(w: number, h: number, flags: number, label: string): number | null {
57
+ const dm = newDevMode()
58
+ if (!enumMode(ENUM_CURRENT_SETTINGS, dm)) {
59
+ print(label + ': EnumDisplaySettings CURRENT failed')
60
+ return null
61
+ }
62
+ const dv = new DataView(dm)
63
+ const before = getPels(dv)
64
+ const size = dv.getUint16(36, true)
65
+ const extra = dv.getUint16(38, true)
66
+ const fields = dv.getUint32(40, true)
67
+ const bpp = dv.getUint32(104, true)
68
+ const freq = dv.getUint32(120, true)
69
+ print(label + ': base size=' + size + ' extra=' + extra + ' fields=0x' + fields.toString(16) +
70
+ ' ' + before[0] + 'x' + before[1] + '@' + freq + ' bpp=' + bpp)
71
+
72
+ dv.setUint32(40, fields | DM_PELSWIDTH | DM_PELSHEIGHT, true)
73
+ dv.setUint32(108, w, true)
74
+ dv.setUint32(112, h, true)
75
+
76
+ const ret = ffi.ffiCall(
77
+ ChangeDisplaySettingsA,
78
+ [ffi.FFI_TYPE_POINTER, ffi.FFI_TYPE_UINT32],
79
+ [dm, flags],
80
+ ffi.FFI_TYPE_SINT32,
81
+ )
82
+ print(label + ': ChangeDisplaySettings ' + w + 'x' + h +
83
+ ' flags=0x' + flags.toString(16) + ' ret=' + ret + ' (' + (DISP_CHANGE[ret] ?? '?') + ')')
84
+ return ret
85
+ }
86
+
87
+ const targetW = Number(scriptArgs[1] ?? '1024')
88
+ const targetH = Number(scriptArgs[2] ?? '768')
89
+ if (!Number.isFinite(targetW) || !Number.isFinite(targetH) || targetW <= 0 || targetH <= 0) {
90
+ print('usage: qwin.exe setres.js [width] [height]')
91
+ std.exit(1)
92
+ }
93
+
94
+ print('before GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()))
95
+
96
+ const probe = newDevMode()
97
+ const modes32: string[] = []
98
+ for (let i = 0; i < 200; i++) {
99
+ if (!enumMode(i, probe)) break
100
+ const dv = new DataView(probe)
101
+ if (dv.getUint32(104, true) !== 32) continue
102
+ const p = getPels(dv)
103
+ modes32.push(p[0] + 'x' + p[1] + '@' + p[2])
104
+ }
105
+ print('32bpp modes sample: ' + modes32.slice(0, 25).join(', ') + (modes32.length > 25 ? ' ...' : ''))
106
+
107
+ const testRet = applyFromCurrent(targetW, targetH, CDS_TEST, 'TEST')
108
+ if (testRet === 0) {
109
+ applyFromCurrent(targetW, targetH, 0, 'DYNAMIC')
110
+ print('after dynamic GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()))
111
+ applyFromCurrent(targetW, targetH, CDS_UPDATEREGISTRY, 'REGISTRY')
112
+ print('after registry GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()))
113
+ } else if (testRet !== null) {
114
+ print('target rejected; try 800x600')
115
+ const t2 = applyFromCurrent(800, 600, CDS_TEST, 'TEST800')
116
+ if (t2 === 0) {
117
+ applyFromCurrent(800, 600, 0, 'DYNAMIC800')
118
+ print('after 800x600 GetScreenSize: ' + JSON.stringify(gui.GetScreenSize()))
119
+ }
120
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "quickwin",
4
- "version": "1.1.4",
4
+ "version": "1.2.0",
5
5
  "description": "QuickJS Win32 runtime — GUI, networking, WASM, FFI, and more",
6
6
  "bin": {
7
7
  "quickwin": "qwin.exe",
package/quickwin.d.ts CHANGED
@@ -330,7 +330,7 @@ declare module "os" {
330
330
  const S_IFREG: number;
331
331
 
332
332
  class Worker {
333
- /** Constructor to create a new thread (worker) with an API close to the `WebWorkers`. `module_filename` is a string specifying the module filename which is executed in the newly created thread. */
333
+ /** Constructor to create a new thread (worker) with an API close to the `WebWorkers`. `module_filename` is a file path, `http(s)://` URL, or `data:text/javascript;base64,...` URL executed in the newly created thread. */
334
334
  constructor(module_filename: string);
335
335
  /** Send a message to the corresponding worker. `msg` is cloned in the destination worker using an algorithm similar to the `HTML` structured clone algorithm. */
336
336
  postMessage(msg: unknown): void;
Binary file
package/qwin-nowasm.exe CHANGED
Binary file
package/qwin-x86.exe CHANGED
Binary file
package/qwin.exe CHANGED
Binary file
package/test/run.js CHANGED
@@ -34,7 +34,9 @@ const suiteDefs = [
34
34
  { name: 'worker', file: './test_worker.js', tags: [] },
35
35
  { name: 'worker-wasm', file: './test_worker_wasm_concurrent.js', tags: ['wasm'] },
36
36
  { name: 'worker-http', file: './test_worker_net.js', tags: ['net'] },
37
- { name: 'worker-fetch', file: './test_worker_fetch.js', tags: ['net'] }
37
+ { name: 'worker-fetch', file: './test_worker_fetch.js', tags: ['net'] },
38
+ { name: 'worker-concurrent', file: './test_worker_concurrent.js', tags: [] },
39
+ { name: 'worker-data-url', file: './test_worker_data_url.js', tags: [] }
38
40
  ];
39
41
  async function main() {
40
42
  const filter = scriptArgs.length > 1 ? scriptArgs[1] : '';
package/test/run.ts CHANGED
@@ -38,7 +38,9 @@ const suiteDefs = [
38
38
  { name: 'worker', file: './test_worker.js', tags: [] },
39
39
  { name: 'worker-wasm', file: './test_worker_wasm_concurrent.js', tags: ['wasm'] },
40
40
  { name: 'worker-http', file: './test_worker_net.js', tags: ['net'] },
41
- { name: 'worker-fetch', file: './test_worker_fetch.js', tags: ['net'] }
41
+ { name: 'worker-fetch', file: './test_worker_fetch.js', tags: ['net'] },
42
+ { name: 'worker-concurrent', file: './test_worker_concurrent.js', tags: [] },
43
+ { name: 'worker-data-url', file: './test_worker_data_url.js', tags: [] }
42
44
  ]
43
45
 
44
46
  async function main(): Promise<void> {
@@ -0,0 +1,45 @@
1
+ import * as os from 'os';
2
+ import * as std from 'std';
3
+ function runRound() {
4
+ const ids = [1, 2];
5
+ return Promise.all(ids.map((id) => new Promise((resolve, reject) => {
6
+ const worker = new os.Worker('./worker_module.js');
7
+ const timer = os.setTimeout(() => {
8
+ worker.onmessage = null;
9
+ reject(new Error(`worker ${id} timeout`));
10
+ }, 5000);
11
+ worker.onmessage = (e) => {
12
+ os.clearTimeout(timer);
13
+ worker.postMessage({ type: 'done' });
14
+ worker.onmessage = null;
15
+ resolve(e.data);
16
+ };
17
+ worker.postMessage({ type: 'start' });
18
+ })));
19
+ }
20
+ export const suite = {
21
+ name: 'worker-concurrent',
22
+ run: async (t) => {
23
+ t.section('dual-worker-init');
24
+ const N = 20;
25
+ let ok = 0;
26
+ let lastErr = null;
27
+ for (let i = 0; i < N; i++) {
28
+ try {
29
+ const results = await runRound();
30
+ if (results.length === 2 &&
31
+ results.every((r) => r.type === 'result' && r.value === 42)) {
32
+ ok++;
33
+ }
34
+ }
35
+ catch (e) {
36
+ lastErr = e;
37
+ break;
38
+ }
39
+ }
40
+ if (ok !== N && lastErr) {
41
+ std.printf('round error: %s\n', String(lastErr));
42
+ }
43
+ t.check('concurrent dual-worker rounds', N, ok);
44
+ }
45
+ };
@@ -0,0 +1,56 @@
1
+ import { Tester } from './test_helper.js'
2
+ import * as os from 'os'
3
+ import * as std from 'std'
4
+
5
+ interface RoundResults {
6
+ type: string
7
+ value: number
8
+ }
9
+
10
+ function runRound(): Promise<RoundResults[]> {
11
+ const ids = [1, 2]
12
+ return Promise.all(ids.map((id) =>
13
+ new Promise<RoundResults>((resolve, reject) => {
14
+ const worker: any = new os.Worker('./worker_module.js')
15
+ const timer = os.setTimeout(() => {
16
+ worker.onmessage = null
17
+ reject(new Error(`worker ${id} timeout`))
18
+ }, 5000)
19
+ worker.onmessage = (e: any) => {
20
+ os.clearTimeout(timer)
21
+ worker.postMessage({ type: 'done' })
22
+ worker.onmessage = null
23
+ resolve(e.data)
24
+ }
25
+ worker.postMessage({ type: 'start' })
26
+ })
27
+ ))
28
+ }
29
+
30
+ export const suite = {
31
+ name: 'worker-concurrent',
32
+ run: async (t: Tester) => {
33
+ t.section('dual-worker-init')
34
+ const N = 20
35
+ let ok = 0
36
+ let lastErr: unknown = null
37
+ for (let i = 0; i < N; i++) {
38
+ try {
39
+ const results = await runRound()
40
+ if (
41
+ results.length === 2 &&
42
+ results.every((r) => r.type === 'result' && r.value === 42)
43
+ ) {
44
+ ok++
45
+ }
46
+ } catch (e) {
47
+ lastErr = e
48
+ break
49
+ }
50
+ }
51
+ if (ok !== N && lastErr) {
52
+ std.printf('round error: %s\n', String(lastErr))
53
+ }
54
+ t.check('concurrent dual-worker rounds', N, ok)
55
+ }
56
+ }
@@ -0,0 +1,46 @@
1
+ import * as os from 'os';
2
+ // base64 of worker body (see comment). Split manually to keep lines short.
3
+ const B64_WORKER = 'aW1wb3J0ICogYXMgb3MgZnJvbSAnb3MnCmNvbnN0IHBhcmVudCA9IG9zLldvcmtlci5wYXJl' +
4
+ 'bnQKcGFyZW50Lm9ubWVzc2FnZSA9IChlKSA9PiB7CiAgICBjb25zdCBkID0gZS5kYXRhCiAg' +
5
+ 'ICBpZiAoZC50eXBlID09PSAnc3RhcnQnKSBwYXJlbnQucG9zdE1lc3NhZ2UoeyB0eXBlOiAn' +
6
+ 'cmVzdWx0JywgdmFsdWU6IDQyIH0pCiAgICBlbHNlIGlmIChkLnR5cGUgPT09ICdkb25lJykg' +
7
+ 'cGFyZW50Lm9ubWVzc2FnZSA9IG51bGwKfQo=';
8
+ const DATA_WORKER = 'data:text/javascript;base64,' + B64_WORKER;
9
+ // export const answer = 42\n
10
+ const DATA_MOD = 'data:text/javascript;base64,ZXhwb3J0IGNvbnN0IGFuc3dlciA9IDQyCg==';
11
+ export const suite = {
12
+ name: 'worker-data-url',
13
+ run: async (t) => {
14
+ t.section('base64-worker-entry');
15
+ let worker;
16
+ try {
17
+ const { data: result } = await new Promise((resolve, reject) => {
18
+ worker = new os.Worker(DATA_WORKER);
19
+ const timer = os.setTimeout(() => reject(new Error('worker timeout')), 5000);
20
+ worker.onmessage = (e) => {
21
+ os.clearTimeout(timer);
22
+ resolve({ data: e.data });
23
+ };
24
+ worker.postMessage({ type: 'start' });
25
+ });
26
+ t.check('message type', 'result', result.type);
27
+ t.check('imported value', 42, result.value);
28
+ worker.postMessage({ type: 'done' });
29
+ worker.onmessage = null;
30
+ }
31
+ catch (e) {
32
+ t.checkTrue('data url worker failed: ' + e, false);
33
+ if (worker) {
34
+ worker.onmessage = null;
35
+ }
36
+ }
37
+ t.section('dynamic-import-data-url');
38
+ try {
39
+ const mod = await import(DATA_MOD);
40
+ t.check('import(data:) answer', 42, mod.answer);
41
+ }
42
+ catch (e) {
43
+ t.checkTrue('import(data:) failed: ' + e, false);
44
+ }
45
+ }
46
+ };
@@ -0,0 +1,51 @@
1
+ import { Tester } from './test_helper.js'
2
+ import * as os from 'os'
3
+
4
+ // base64 of worker body (see comment). Split manually to keep lines short.
5
+ const B64_WORKER =
6
+ 'aW1wb3J0ICogYXMgb3MgZnJvbSAnb3MnCmNvbnN0IHBhcmVudCA9IG9zLldvcmtlci5wYXJl' +
7
+ 'bnQKcGFyZW50Lm9ubWVzc2FnZSA9IChlKSA9PiB7CiAgICBjb25zdCBkID0gZS5kYXRhCiAg' +
8
+ 'ICBpZiAoZC50eXBlID09PSAnc3RhcnQnKSBwYXJlbnQucG9zdE1lc3NhZ2UoeyB0eXBlOiAn' +
9
+ 'cmVzdWx0JywgdmFsdWU6IDQyIH0pCiAgICBlbHNlIGlmIChkLnR5cGUgPT09ICdkb25lJykg' +
10
+ 'cGFyZW50Lm9ubWVzc2FnZSA9IG51bGwKfQo='
11
+
12
+ const DATA_WORKER = 'data:text/javascript;base64,' + B64_WORKER
13
+
14
+ // export const answer = 42\n
15
+ const DATA_MOD = 'data:text/javascript;base64,ZXhwb3J0IGNvbnN0IGFuc3dlciA9IDQyCg=='
16
+
17
+ export const suite = {
18
+ name: 'worker-data-url',
19
+ run: async (t: Tester) => {
20
+ t.section('base64-worker-entry')
21
+ let worker: any
22
+ try {
23
+ const { data: result } = await new Promise<any>((resolve, reject) => {
24
+ worker = new os.Worker(DATA_WORKER)
25
+ const timer = os.setTimeout(() => reject(new Error('worker timeout')), 5000)
26
+ worker.onmessage = (e: any) => {
27
+ os.clearTimeout(timer)
28
+ resolve({ data: e.data })
29
+ }
30
+ worker.postMessage({ type: 'start' })
31
+ })
32
+ t.check('message type', 'result', result.type)
33
+ t.check('imported value', 42, result.value)
34
+ worker.postMessage({ type: 'done' })
35
+ worker.onmessage = null
36
+ } catch (e) {
37
+ t.checkTrue('data url worker failed: ' + e, false)
38
+ if (worker) {
39
+ worker.onmessage = null
40
+ }
41
+ }
42
+
43
+ t.section('dynamic-import-data-url')
44
+ try {
45
+ const mod = await import(DATA_MOD)
46
+ t.check('import(data:) answer', 42, (mod as any).answer)
47
+ } catch (e) {
48
+ t.checkTrue('import(data:) failed: ' + e, false)
49
+ }
50
+ }
51
+ }