wingo opened issue #11652:
Test case
use std::process; extern crate wit_bindgen; wit_bindgen::generate!({ inline: r" package test:test; world test { include wasi:filesystem/imports@0.3.0-rc-2025-08-15; include wasi:cli/command@0.3.0-rc-2025-08-15; } ", additional_derives: [PartialEq, Eq, Hash, Clone], // Work around https://github.com/bytecodealliance/wasm-tools/issues/2285. features:["clocks-timezone"], async: [ "wasi:cli/run@0.3.0-rc-2025-08-15#run", ], generate_all }); async fn test_filesystem() { match &wasi::filesystem::preopens::get_directories()[..] { [(dir, _)] => { match dir.symlink_at("..".to_string(), "parent".to_string()).await { Ok(()) => {}, Err(wasi::filesystem::types::ErrorCode::Exist) => {}, Err(err) => { panic!("{}", err); } } let (stream, result) = dir.read_directory().await; stream.collect().await; result.await.unwrap(); }, [..] => { eprintln!("usage: run with one open dir"); process::exit(1) } } } struct Component; export!(Component); impl exports::wasi::cli::run::Guest for Component { async fn run() -> Result<(), ()> { test_filesystem().await; Ok(()) } } fn main() { unreachable!("main is a stub"); }Run via
wasmtime --dir t -Wcomponent-model-async=y -Sp3=y test.wasm.Expected behavior
The
stream.collect()completes. But it doesn't: it iloops. Here's a snippet from the strace:statx(11, "parent", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFLNK|0777, stx_size=2, ...}) = 0 getdents64(12, 0x56340a5814e0 /* 0 entries */, 1536) = 0 close(12) = 0 close(11) = 0 openat(3, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 11 fcntl(11, F_GETFL) = 0x38000 (flags O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_DIRECTORY) openat(11, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 12 getdents64(12, 0x56340a581150 /* 3 entries */, 768) = 80 statx(11, "parent", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFLNK|0777, stx_size=2, ...}) = 0 getdents64(12, 0x56340a5814e0 /* 0 entries */, 1536) = 0 close(12) = 0 close(11) = 0 openat(3, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 11 fcntl(11, F_GETFL) = 0x38000 (flags O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_DIRECTORY) openat(11, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 12 getdents64(12, 0x56340a581150 /* 3 entries */, 768) = 80 statx(11, "parent", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFLNK|0777, stx_size=2, ...}) = 0 getdents64(12, 0x56340a5814e0 /* 0 entries */, 1536) = 0 close(12) = 0 close(11) = 0 openat(3, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 11 fcntl(11, F_GETFL) = 0x38000 (flags O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_DIRECTORY) openat(11, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 12 getdents64(12, 0x56340a581150 /* 3 entries */, 768) = 80 statx(11, "parent", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFLNK|0777, stx_size=2, ...}) = 0 getdents64(12, 0x56340a5814e0 /* 0 entries */, 1536) = 0 close(12) = 0 close(11) = 0 openat(3, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 11 fcntl(11, F_GETFL) = 0x38000 (flags O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_DIRECTORY) openat(11, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 12 getdents64(12, 0x56340a581150 /* 3 entries */, 768) = 80 statx(11, "parent", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFLNK|0777, stx_size=2, ...}) = 0 getdents64(12, 0x56340a5814e0 /* 0 entries */, 1536) = 0 close(12) = 0 close(11) = 0
wingo added the bug label to Issue #11652.
alexcrichton commented on issue #11652:
Could you clarify what version of Wasmtime you're using? On
mainif I apply this patch:diff --git a/crates/test-programs/src/bin/p3_readdir_loop.rs b/crates/test-programs/src/bin/p3_readdir_loop.rs new file mode 100644 index 0000000000..e188a022f3 --- /dev/null +++ b/crates/test-programs/src/bin/p3_readdir_loop.rs @@ -0,0 +1,31 @@ +use test_programs::p3::wasi; + +struct Component; + +test_programs::p3::export!(Component); + +impl test_programs::p3::exports::wasi::cli::run::Guest for Component { + async fn run() -> Result<(), ()> { + match &wasi::filesystem::preopens::get_directories()[..] { + [(dir, _)] => { + match dir.symlink_at("..".to_string(), "parent".to_string()).await { + Ok(()) => {} + Err(wasi::filesystem::types::ErrorCode::Exist) => {} + Err(err) => { + panic!("{}", err); + } + } + let (stream, result) = dir.read_directory().await; + stream.collect().await; + result.await.unwrap(); + Ok(()) + } + [..] => { + eprintln!("usage: run with one open dir"); + Err(()) + } + } + } +} + +fn main() {} diff --git a/crates/wasi/tests/all/p3/mod.rs b/crates/wasi/tests/all/p3/mod.rs index 6533d9d3f0..a47702df6e 100644 --- a/crates/wasi/tests/all/p3/mod.rs +++ b/crates/wasi/tests/all/p3/mod.rs @@ -142,3 +142,8 @@ async fn p3_readdir() -> anyhow::Result<()> { async fn p3_readdir_blocking() -> anyhow::Result<()> { run_allow_blocking_current_thread(P3_READDIR_COMPONENT, true).await } + +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p3_readdir_loop() -> anyhow::Result<()> { + run(P3_READDIR_LOOP_COMPONENT).await +}the test passes:
$ cargo test -p wasmtime-wasi --test all --features p3 -- p3_readdir_loop Finished `test` profile [unoptimized + debuginfo] target(s) in 0.08s Running tests/all/main.rs (target/x86_64-unknown-linux-gnu/debug/deps/all-50fb7a1e72c4f7c2) running 1 test test p3::p3_readdir_loop ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 215 filtered out; finished in 1.38s
wingo commented on issue #11652:
I was using 10d2cbc59d35b14c3027a8542f636c591b2ec96b. Testing with current main (447efbd330074928381df2c67d83cd84c810843b) gives me the same results (the iloop), for my test. Puzzling~
wingo commented on issue #11652:
Here's a tarball that exhibits the bug for me: test-readdir.tar.gz
Run via:
cargo +nightly build --target=wasm32-wasip2 --release && strace wasmtime --dir t -Wcomponent-model-async=y -Sp3=y target/wasm32-wasip2/release/test-readdir.wasm
alexcrichton commented on issue #11652:
Hm ok I can definitely not reproduce this. In the interest of making this more reproducible here's a
Dockerfile:FROM ubuntu:24.04 RUN apt-get update -y && apt-get install -y gcc curl xz-utils RUN curl -LO https://github.com/user-attachments/files/22252013/test-readdir.tar.gz RUN tar xf test-readdir.tar.gz WORKDIR /test-readdir RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ bash -s -- -y --default-toolchain nightly ENV PATH=$PATH:/root/.cargo/bin RUN rustup target add wasm32-wasip2 RUN cargo +nightly build --target=wasm32-wasip2 --release RUN curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/dev/wasmtime-dev-x86_64-linux.tar.xz RUN tar xf wasmtime-dev-x86_64-linux.tar.xz RUN ./wasmtime-dev-x86_64-linux/wasmtime --dir t -Wcomponent-model-async=y -Sp3=y target/wasm32-wasip2/release/test-readdir.wasmwhich if I put that in a folder as
Dockerfileand rundocker build .then the build passes and doesn't infinite loop.Is this perhaps filesystem-dependent behavior? I'm on a pretty much stock Ubuntu 24.04 machine with an ext4 filesystem and kernel
6.8.0-79-generic. Are you able to reproduce with the docker script? Or are you on a different filesystem or a different kernel perhaps?
wingo commented on issue #11652:
Well! Managed to track it down. It's fixed in 5764da5fd5afdebaeca926908565b875e0220884; could it be that I just fetched wasmtime instead of building, before? Weird, but I'm not crazy at least; Dockerfile to repro:
FROM ubuntu:24.04 RUN apt-get update -y && apt-get install -y gcc curl xz-utils git RUN curl -LO https://github.com/user-attachments/files/22252013/test-readdir.tar.gz RUN tar xf test-readdir.tar.gz WORKDIR /test-readdir RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ bash -s -- -y --default-toolchain nightly ENV PATH=$PATH:/root/.cargo/bin RUN rustup target add wasm32-wasip2 RUN cargo +nightly build --target=wasm32-wasip2 --release RUN git clone https://github.com/bytecodealliance/wasmtime RUN cd wasmtime && git checkout e3561d515f1c217c80c634a12c17d6797ce1332a && git submodule update RUN cargo +nightly build --features component-model-async --release --manifest-path wasmtime/Cargo.toml RUN ./wasmtime/target/release/wasmtime --dir t -Wcomponent-model-async=y -Sp3=y target/wasm32-wasip2/release/test-readdir.wasm
alexcrichton closed issue #11652:
Test case
use std::process; extern crate wit_bindgen; wit_bindgen::generate!({ inline: r" package test:test; world test { include wasi:filesystem/imports@0.3.0-rc-2025-08-15; include wasi:cli/command@0.3.0-rc-2025-08-15; } ", additional_derives: [PartialEq, Eq, Hash, Clone], // Work around https://github.com/bytecodealliance/wasm-tools/issues/2285. features:["clocks-timezone"], async: [ "wasi:cli/run@0.3.0-rc-2025-08-15#run", ], generate_all }); async fn test_filesystem() { match &wasi::filesystem::preopens::get_directories()[..] { [(dir, _)] => { match dir.symlink_at("..".to_string(), "parent".to_string()).await { Ok(()) => {}, Err(wasi::filesystem::types::ErrorCode::Exist) => {}, Err(err) => { panic!("{}", err); } } let (stream, result) = dir.read_directory().await; stream.collect().await; result.await.unwrap(); }, [..] => { eprintln!("usage: run with one open dir"); process::exit(1) } } } struct Component; export!(Component); impl exports::wasi::cli::run::Guest for Component { async fn run() -> Result<(), ()> { test_filesystem().await; Ok(()) } } fn main() { unreachable!("main is a stub"); }Run via
wasmtime --dir t -Wcomponent-model-async=y -Sp3=y test.wasm.Expected behavior
The
stream.collect()completes. But it doesn't: it iloops. Here's a snippet from the strace:statx(11, "parent", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFLNK|0777, stx_size=2, ...}) = 0 getdents64(12, 0x56340a5814e0 /* 0 entries */, 1536) = 0 close(12) = 0 close(11) = 0 openat(3, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 11 fcntl(11, F_GETFL) = 0x38000 (flags O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_DIRECTORY) openat(11, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 12 getdents64(12, 0x56340a581150 /* 3 entries */, 768) = 80 statx(11, "parent", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFLNK|0777, stx_size=2, ...}) = 0 getdents64(12, 0x56340a5814e0 /* 0 entries */, 1536) = 0 close(12) = 0 close(11) = 0 openat(3, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 11 fcntl(11, F_GETFL) = 0x38000 (flags O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_DIRECTORY) openat(11, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 12 getdents64(12, 0x56340a581150 /* 3 entries */, 768) = 80 statx(11, "parent", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFLNK|0777, stx_size=2, ...}) = 0 getdents64(12, 0x56340a5814e0 /* 0 entries */, 1536) = 0 close(12) = 0 close(11) = 0 openat(3, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 11 fcntl(11, F_GETFL) = 0x38000 (flags O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_DIRECTORY) openat(11, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 12 getdents64(12, 0x56340a581150 /* 3 entries */, 768) = 80 statx(11, "parent", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFLNK|0777, stx_size=2, ...}) = 0 getdents64(12, 0x56340a5814e0 /* 0 entries */, 1536) = 0 close(12) = 0 close(11) = 0 openat(3, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 11 fcntl(11, F_GETFL) = 0x38000 (flags O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_DIRECTORY) openat(11, ".", O_RDONLY|O_LARGEFILE|O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY) = 12 getdents64(12, 0x56340a581150 /* 3 entries */, 768) = 80 statx(11, "parent", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL, {stx_mask=STATX_ALL|STATX_MNT_ID, stx_attributes=0, stx_mode=S_IFLNK|0777, stx_size=2, ...}) = 0 getdents64(12, 0x56340a5814e0 /* 0 entries */, 1536) = 0 close(12) = 0 close(11) = 0
alexcrichton commented on issue #11652:
Aha that makes sense, thanks for tracking that down! That change was accompanied with its own tests/refactorings so I think it's ok to not test this specifically in the repo, so I'll close this as fixed by https://github.com/bytecodealliance/wasmtime/pull/11515
Last updated: Dec 06 2025 at 06:05 UTC