Stream: git-wasmtime

Topic: wasmtime / issue #11652 iloop when readdir on dir contain...


view this post on Zulip Wasmtime GitHub notifications bot (Sep 09 2025 at 15:04):

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

view this post on Zulip Wasmtime GitHub notifications bot (Sep 09 2025 at 15:04):

wingo added the bug label to Issue #11652.

view this post on Zulip Wasmtime GitHub notifications bot (Sep 09 2025 at 15:38):

alexcrichton commented on issue #11652:

Could you clarify what version of Wasmtime you're using? On main if 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

view this post on Zulip Wasmtime GitHub notifications bot (Sep 10 2025 at 07:51):

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~

view this post on Zulip Wasmtime GitHub notifications bot (Sep 10 2025 at 09:37):

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

view this post on Zulip Wasmtime GitHub notifications bot (Sep 10 2025 at 19:03):

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.wasm

which if I put that in a folder as Dockerfile and run docker 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?

view this post on Zulip Wasmtime GitHub notifications bot (Sep 11 2025 at 07:41):

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

view this post on Zulip Wasmtime GitHub notifications bot (Sep 11 2025 at 17:26):

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

view this post on Zulip Wasmtime GitHub notifications bot (Sep 11 2025 at 17:26):

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