Skip to content

Commit 70ab999

Browse files
committed
wip
1 parent 9bc3483 commit 70ab999

30 files changed

+1163
-21
lines changed

threads/bun.lockb

3.11 KB
Binary file not shown.
1.95 MB
Binary file not shown.
1.6 MB
Binary file not shown.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>WASIFarmAnimal destroy() test</title>
6+
</head>
7+
<body>
8+
<h1>WASIFarmAnimal destroy() test</h1>
9+
<p id="note">
10+
This example demonstrates the destroy() method of WASIFarmAnimal.
11+
Check the console for output.
12+
</p>
13+
<p>
14+
<a href="https://github.com/bjorn3/browser_wasi_shim/blob/main/threads/examples/destroy/README.md" target="_blank">
15+
📖 Read the documentation
16+
</a>
17+
</p>
18+
<button id="run-test">Run Test</button>
19+
<pre id="output"></pre>
20+
21+
<script type="module" src="./index.ts"></script>
22+
</body>
23+
</html>

threads/examples/destroy/index.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import {
2+
OpenFile,
3+
File,
4+
ConsoleStdout,
5+
PreopenDirectory,
6+
type Inode,
7+
Directory,
8+
} from "@bjorn3/browser_wasi_shim";
9+
import { WASIFarm } from "../../src";
10+
11+
const outputEl = document.getElementById("output") as HTMLPreElement;
12+
const runBtn = document.getElementById("run-test") as HTMLButtonElement;
13+
14+
function log(msg: string) {
15+
console.log(msg);
16+
outputEl.textContent += msg + "\n";
17+
}
18+
19+
runBtn.addEventListener("click", async () => {
20+
outputEl.textContent = "";
21+
22+
log("=== Testing WASIFarmAnimal.destroy() ===\n");
23+
log("This test runs eternal_loop.wasm with threads.");
24+
log("Watch the console output - it should stop after destroy() is called.\n");
25+
26+
const worker = new Worker("./worker.ts", { type: "module" });
27+
28+
worker.onmessage = async (event) => {
29+
if (event.data.wasi_ref) {
30+
const wasi_ref2 = event.data.wasi_ref;
31+
log("✓ Worker initialized");
32+
33+
const current_directory = new Map<string, Inode>();
34+
current_directory.set(
35+
"hello.txt",
36+
new File(new TextEncoder().encode("Hello, world!")),
37+
);
38+
current_directory.set("hello2", new Directory(new Map()));
39+
40+
const wasi_farm = new WASIFarm(
41+
new OpenFile(new File([])),
42+
ConsoleStdout.lineBuffered((msg) => log(`[WASI stdout] ${msg}`)),
43+
ConsoleStdout.lineBuffered((msg) => log(`[WASI stderr] ${msg}`)),
44+
[new PreopenDirectory(".", current_directory)],
45+
);
46+
log("✓ WASI farm created");
47+
48+
const wasi_ref = await wasi_farm.get_ref();
49+
log("✓ WASI ref obtained\n");
50+
51+
const testWorker = new Worker("./test_worker.ts", { type: "module" });
52+
53+
testWorker.onmessage = (e) => {
54+
if (e.data.started) {
55+
log("✓ Eternal loop with threads started");
56+
log("✓ Check console - you should see thread output");
57+
log("✓ Waiting 5 seconds before calling destroy()...\n");
58+
}
59+
60+
if (e.data.destroyed) {
61+
log("\n✓ destroy() called");
62+
log("✓ All threads should now be terminated");
63+
log("✓ Check console - thread output should have stopped\n");
64+
65+
setTimeout(() => {
66+
log("✓ TEST COMPLETED");
67+
log(
68+
" If thread output stopped in console after destroy(), the test passed!",
69+
);
70+
}, 2000);
71+
}
72+
73+
if (e.data.error) {
74+
log(`✗ Error: ${e.data.error}`);
75+
}
76+
};
77+
78+
testWorker.postMessage({ wasi_ref, wasi_ref2 });
79+
log("✓ Test worker started\n");
80+
}
81+
};
82+
});

threads/examples/destroy/main.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
use std::thread;
2+
// rustc +nightly --target wasm32-wasip1-threads main.rs -o eternal_loop.wasm
3+
// wasm-opt -Oz eternal_loop.wasm -o eternal_loop_opt.wasm
4+
5+
fn sleep_ms(ms: u64) {
6+
fn get_time_ms() -> u128 {
7+
let now = std::time::SystemTime::now();
8+
let since_epoch = now.duration_since(std::time::UNIX_EPOCH).unwrap();
9+
since_epoch.as_millis()
10+
}
11+
12+
let start = get_time_ms();
13+
loop {
14+
let now = get_time_ms();
15+
if now - start >= ms as u128 {
16+
break;
17+
}
18+
std::thread::yield_now();
19+
}
20+
}
21+
22+
fn main() {
23+
println!("Starting multi-threaded eternal loop demo...");
24+
println!("Spawning 3 threads that will run forever until destroy() is called");
25+
26+
let mut handles = vec![];
27+
28+
// Spawn thread 1
29+
let handle1 = thread::spawn(|| {
30+
let mut counter = 0u64;
31+
loop {
32+
counter += 1;
33+
println!("Thread 1: iteration {}", counter);
34+
sleep_ms(100);
35+
}
36+
});
37+
handles.push(handle1);
38+
39+
// Spawn thread 2
40+
let handle2 = thread::spawn(|| {
41+
let mut counter = 0u64;
42+
loop {
43+
counter += 1;
44+
println!("Thread 2: iteration {}", counter);
45+
sleep_ms(150);
46+
}
47+
});
48+
handles.push(handle2);
49+
50+
// Spawn thread 3
51+
let handle3 = thread::spawn(|| {
52+
let mut counter = 0u64;
53+
loop {
54+
counter += 1;
55+
println!("Thread 3: iteration {}", counter);
56+
sleep_ms(200);
57+
}
58+
});
59+
handles.push(handle3);
60+
61+
println!("All threads spawned, waiting for them to complete...");
62+
println!("(They won't complete unless destroy() terminates the workers)");
63+
64+
// Main thread also loops
65+
let mut main_counter = 0u64;
66+
loop {
67+
main_counter += 1;
68+
println!("Main thread: iteration {}", main_counter);
69+
70+
if main_counter % 10 == 0 {
71+
println!(
72+
"Main thread: {} iterations completed, child threads still running",
73+
main_counter
74+
);
75+
}
76+
77+
sleep_ms(120);
78+
}
79+
80+
// This will never be reached unless destroy() kills the workers
81+
// for handle in handles {
82+
// handle.join().unwrap();
83+
// }
84+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { WASIFarmAnimal } from "../../src";
2+
3+
// Test worker that runs eternal_loop.wasm and monitors its output
4+
self.onmessage = async (e) => {
5+
const { wasi_ref, wasi_ref2 } = e.data;
6+
7+
console.log("TestWorker: Loading eternal_loop.wasm");
8+
9+
try {
10+
const wasmResponse = await fetch("./eternal_loop.wasm");
11+
const wasmBuffer = await wasmResponse.arrayBuffer();
12+
const wasm = await WebAssembly.compile(wasmBuffer);
13+
14+
console.log("TestWorker: Creating WASIFarmAnimal with thread spawner");
15+
16+
const wasi = new WASIFarmAnimal([wasi_ref2, wasi_ref], [], [], {
17+
can_thread_spawn: true,
18+
thread_spawn_worker_url: new URL("./thread_spawn.ts", import.meta.url)
19+
.href,
20+
thread_spawn_wasm: wasm,
21+
worker_background_worker_url: new URL(
22+
"./worker_background.ts",
23+
import.meta.url,
24+
).href,
25+
});
26+
27+
console.log("TestWorker: Waiting for background worker...");
28+
await wasi.wait_worker_background_worker();
29+
console.log("TestWorker: Background worker ready");
30+
31+
const inst = await WebAssembly.instantiate(wasm, {
32+
env: {
33+
...wasi.get_share_memory(),
34+
},
35+
wasi: wasi.wasiThreadImport,
36+
wasi_snapshot_preview1: wasi.wasiImport,
37+
});
38+
39+
console.log("TestWorker: Starting eternal loop with threads");
40+
self.postMessage({ started: true });
41+
42+
// Start WASM (runs forever with threads printing)
43+
setTimeout(() => {
44+
try {
45+
wasi.start(
46+
inst as unknown as {
47+
exports: { memory: WebAssembly.Memory; _start: () => unknown };
48+
},
49+
);
50+
console.log("TestWorker: WASM completed (unexpected!)");
51+
} catch (error) {
52+
console.log("TestWorker: WASM terminated (expected after destroy)");
53+
}
54+
}, 100);
55+
56+
// Wait 5 seconds then destroy
57+
await new Promise((resolve) => setTimeout(resolve, 5000));
58+
59+
console.log("TestWorker: Calling destroy() to terminate all threads...");
60+
wasi.destroy();
61+
console.log("TestWorker: destroy() completed - all threads terminated");
62+
63+
self.postMessage({ destroyed: true });
64+
} catch (error) {
65+
console.error("TestWorker: Error:", error);
66+
self.postMessage({ error: String(error) });
67+
}
68+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { thread_spawn_on_worker } from "../../src";
2+
3+
self.onmessage = (event) => {
4+
thread_spawn_on_worker(event.data);
5+
};

threads/examples/destroy/worker.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {
2+
ConsoleStdout,
3+
type Inode,
4+
PreopenDirectory,
5+
File,
6+
} from "@bjorn3/browser_wasi_shim";
7+
import { WASIFarm } from "../../src";
8+
9+
const dir = new Map<string, Inode>();
10+
dir.set("hello2.txt", new File(new TextEncoder().encode("Hello, world!!!!!")));
11+
12+
const wasi_farm = new WASIFarm(
13+
undefined,
14+
ConsoleStdout.lineBuffered((msg) =>
15+
console.log(`[WASI stdout on worker] ${msg}`),
16+
),
17+
undefined,
18+
[new PreopenDirectory("hello2", dir)],
19+
);
20+
21+
console.log("WASI farm created");
22+
const wasi_ref = await wasi_farm.get_ref();
23+
self.postMessage({ wasi_ref });
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// @ts-ignore
2+
import worker_background_worker from "../node_modules/@oligami/browser_wasi_shim-threads/dist/worker_background_worker.min.js";
3+
4+
import { wait_async_polyfill } from "../../src/index.js";
5+
6+
wait_async_polyfill();
7+
8+
worker_background_worker();

0 commit comments

Comments
 (0)