Repository: bytecodealliance/wasmtime
Pull request #7802 (2024-01-20 15:49:17)

WASI: remove unnecessary Sync constraints
Fundamentally, preview2 is fully synchronous. So at least in theory there should be no need for all the Sync constraints. In practice it took me a while to figure out how to actually make it work. In summary:
- Removed many `+ Sync` constraints
- Removed the immutable reference accessors on WasiView. I also dropped the _mut suffix for consistency with WasiHttpView.
- Had to do a bit of refactoring in `crates/wasi/src/preview2/host/filesystem.rs` [here](https://github.com/bytecodealliance/wasmtime/commit/6b4d634c8ea0de212229c0e9528e06598dacbd22#diff-dff18465028496004c5e0e50b9d35bc5303ea19e2c50d6f1284dd45c6fe7a2c4) and `crates/wasi/src/preview2/preview1.rs` [here](https://github.com/bytecodealliance/wasmtime/commit/1e766c240df9081fcd2b617e1bc76a63dc719363) to make them play along.

---

Chat: https://bytecodealliance.zulipchat.com/#narrow/stream/217126-wasmtime/topic/Wasmtime.20preview2.20.60Sync.60

github-actions[bot]: #### Subscribe to Label Action

cc @peterhuene

<details>
This issue or pull request has been labeled: "wasi", "wasmtime:api"

Thus the following users have been cc'd because of the following labels:

* peterhuene: wasmtime:api

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>
badeend: There were some build errors specific to Rust version 1.73 (and lower probably).

This should be good to go again.

Patch:
diff --git a/crates/wasi/src/preview2/clocks.rs b/crates/wasi/src/preview2/clocks.rs
index 53bca0507..175ac1990 100644
--- a/crates/wasi/src/preview2/clocks.rs
+++ b/crates/wasi/src/preview2/clocks.rs
@@ -1,12 +1,12 @@
 pub mod host;
 use cap_std::time::Duration;
 
-pub trait HostWallClock: Send + Sync {
+pub trait HostWallClock: Send {
     fn resolution(&self) -> Duration;
     fn now(&self) -> Duration;
 }
 
-pub trait HostMonotonicClock: Send + Sync {
+pub trait HostMonotonicClock: Send {
     fn resolution(&self) -> u64;
     fn now(&self) -> u64;
 }
diff --git a/crates/wasi/src/preview2/clocks/host.rs b/crates/wasi/src/preview2/clocks/host.rs
index e0c9906c7..20b35ce64 100644
--- a/crates/wasi/src/preview2/clocks/host.rs
+++ b/crates/wasi/src/preview2/clocks/host.rs
@@ -64,10 +64,10 @@ impl HostMonotonicClock for MonotonicClock {
     }
 }
 
-pub fn monotonic_clock() -> Box<dyn HostMonotonicClock + Send + Sync> {
+pub fn monotonic_clock() -> Box<dyn HostMonotonicClock + Send> {
     Box::new(MonotonicClock::new(ambient_authority()))
 }
 
-pub fn wall_clock() -> Box<dyn HostWallClock + Send + Sync> {
+pub fn wall_clock() -> Box<dyn HostWallClock + Send> {
     Box::new(WallClock::new(ambient_authority()))
 }
diff --git a/crates/wasi/src/preview2/ctx.rs b/crates/wasi/src/preview2/ctx.rs
index a0447711a..97c0119f3 100644
--- a/crates/wasi/src/preview2/ctx.rs
+++ b/crates/wasi/src/preview2/ctx.rs
@@ -22,11 +22,11 @@ pub struct WasiCtxBuilder {
     args: Vec<String>,
     preopens: Vec<(Dir, String)>,
     socket_addr_check: SocketAddrCheck,
-    random: Box<dyn RngCore + Send + Sync>,
-    insecure_random: Box<dyn RngCore + Send + Sync>,
+    random: Box<dyn RngCore + Send>,
+    insecure_random: Box<dyn RngCore + Send>,
     insecure_random_seed: u128,
-    wall_clock: Box<dyn HostWallClock + Send + Sync>,
-    monotonic_clock: Box<dyn HostMonotonicClock + Send + Sync>,
+    wall_clock: Box<dyn HostWallClock + Send>,
+    monotonic_clock: Box<dyn HostMonotonicClock + Send>,
     allowed_network_uses: AllowedNetworkUses,
     built: bool,
 }
@@ -159,15 +159,12 @@ impl WasiCtxBuilder {
     /// unpredictable random data in order to maintain its security invariants,
     /// and ideally should use the insecure random API otherwise, so using any
     /// prerecorded or otherwise predictable data may compromise security.
-    pub fn secure_random(&mut self, random: impl RngCore + Send + Sync + 'static) -> &mut Self {
+    pub fn secure_random(&mut self, random: impl RngCore + Send + 'static) -> &mut Self {
         self.random = Box::new(random);
         self
     }
 
-    pub fn insecure_random(
-        &mut self,
-        insecure_random: impl RngCore + Send + Sync + 'static,
-    ) -> &mut Self {
+    pub fn insecure_random(&mut self, insecure_random: impl RngCore + Send + 'static) -> &mut Self {
         self.insecure_random = Box::new(insecure_random);
         self
     }
@@ -270,18 +267,16 @@ impl WasiCtxBuilder {
 }
 
 pub trait WasiView: Send {
-    fn table(&self) -> &ResourceTable;
-    fn table_mut(&mut self) -> &mut ResourceTable;
-    fn ctx(&self) -> &WasiCtx;
-    fn ctx_mut(&mut self) -> &mut WasiCtx;
+    fn table(&mut self) -> &mut ResourceTable;
+    fn ctx(&mut self) -> &mut WasiCtx;
 }
 
 pub struct WasiCtx {
-    pub(crate) random: Box<dyn RngCore + Send + Sync>,
-    pub(crate) insecure_random: Box<dyn RngCore + Send + Sync>,
+    pub(crate) random: Box<dyn RngCore + Send>,
+    pub(crate) insecure_random: Box<dyn RngCore + Send>,
     pub(crate) insecure_random_seed: u128,
-    pub(crate) wall_clock: Box<dyn HostWallClock + Send + Sync>,
-    pub(crate) monotonic_clock: Box<dyn HostMonotonicClock + Send + Sync>,
+    pub(crate) wall_clock: Box<dyn HostWallClock + Send>,
+    pub(crate) monotonic_clock: Box<dyn HostMonotonicClock + Send>,
     pub(crate) env: Vec<(String, String)>,
     pub(crate) args: Vec<String>,
     pub(crate) preopens: Vec<(Dir, String)>,
diff --git a/crates/wasi/src/preview2/host/clocks.rs b/crates/wasi/src/preview2/host/clocks.rs
index 2f16b9e10..42b6cb7b9 100644
--- a/crates/wasi/src/preview2/host/clocks.rs
+++ b/crates/wasi/src/preview2/host/clocks.rs
@@ -77,11 +77,11 @@ impl<T: WasiView> monotonic_clock::Host for T {
         } else {
             Duration::from_nanos(0)
         };
-        subscribe_to_duration(&mut self.table_mut(), duration)
+        subscribe_to_duration(&mut self.table(), duration)
     }
 
     fn subscribe_duration(&mut self, duration: WasiDuration) -> anyhow::Result<Resource<Pollable>> {
-        subscribe_to_duration(&mut self.table_mut(), Duration::from_nanos(duration))
+        subscribe_to_duration(&mut self.table(), Duration::from_nanos(duration))
     }
 }
 
diff --git a/crates/wasi/src/preview2/host/filesystem.rs b/crates/wasi/src/preview2/host/filesystem.rs
index 4a70efee5..f2b67eca0 100644
--- a/crates/wasi/src/preview2/host/filesystem.rs
+++ b/crates/wasi/src/preview2/host/filesystem.rs
@@ -8,7 +8,7 @@ use crate::preview2::filesystem::{Descriptor, Dir, File, ReaddirIterator};
 use crate::preview2::filesystem::{FileInputStream, FileOutputStream};
 use crate::preview2::{DirPerms, FilePerms, FsError, FsResult, WasiView};
 use anyhow::Context;
-use wasmtime::component::{Resource, ResourceTable};
+use wasmtime::component::Resource;
 
 mod sync;
 
@@ -19,7 +19,7 @@ impl<T: WasiView> preopens::Host for T {
         let mut results = Vec::new();
         for (dir, name) in self.ctx().preopens.clone() {
             let fd = self
-                .table_mut()
+                .table()
                 .push(Descriptor::Dir(dir))
                 .with_context(|| format!("failed to push preopen {name}"))?;
             results.push((fd, name));
@@ -38,7 +38,7 @@ impl<T: WasiView> types::Host for T {
         &mut self,
         err: Resource<anyhow::Error>,
     ) -> anyhow::Result<Option<ErrorCode>> {
-        let err = self.table_mut().get(&err)?;
+        let err = self.table().get(&err)?;
 
         // Currently `err` always comes from the stream implementation which
         // uses standard reads/writes so only check for `std::io::Error` here.
@@ -78,9 +78,9 @@ impl<T: WasiView> HostDescriptor for T {
     }
 
     async fn sync_data(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
-        let table = self.table();
+        let descriptor = self.table().get(&fd)?;
 
-        match table.get(&fd)? {
+        match descriptor {
             Descriptor::File(f) => {
                 match f.spawn_blocking(|f| f.sync_data()).await {
                     Ok(()) => Ok(()),
@@ -125,8 +125,8 @@ impl<T: WasiView> HostDescriptor for T {
             out
         }
 
-        let table = self.table();
-        match table.get(&fd)? {
+        let descriptor = self.table().get(&fd)?;
+        match descriptor {
             Descriptor::File(f) => {
                 let flags = f.spawn_blocking(|f| f.get_fd_flags()).await?;
                 let mut flags = get_from_fdflags(flags);
@@ -156,9 +156,9 @@ impl<T: WasiView> HostDescriptor for T {
         &mut self,
         fd: Resource<types::Descriptor>,
     ) -> FsResult<types::DescriptorType> {
-        let table = self.table();
+        let descriptor = self.table().get(&fd)?;
 
-        match table.get(&fd)? {
+        match descriptor {
             Descriptor::File(f) => {
                 let meta = f.spawn_blocking(|f| f.metadata()).await?;
                 Ok(descriptortype_from(meta.file_type()))
@@ -188,8 +188,8 @@ impl<T: WasiView> HostDescriptor for T {
     ) -> FsResult<()> {
         use fs_set_times::SetTimes;
 
-        let table = self.table();
-        match table.get(&fd)? {
+        let descriptor = self.table().get(&fd)?;
+        match descriptor {
             Descriptor::File(f) => {
                 if !f.perms.contains(FilePerms::WRITE) {
                     return Err(ErrorCode::NotPermitted.into());
@@ -275,7 +275,7 @@ impl<T: WasiView> HostDescriptor for T {
         &mut self,
         fd: Resource<types::Descriptor>,
     ) -> FsResult<Resource<types::DirectoryEntryStream>> {
-        let table = self.table_mut();
+        let table = self.table();
         let d = table.get(&fd)?.dir()?;
         if !d.perms.contains(DirPerms::READ) {
             return Err(ErrorCode::NotPermitted.into());
@@ -337,9 +337,9 @@ impl<T: WasiView> HostDescriptor for T {
     }
 
     async fn sync(&mut self, fd: Resource<types::Descriptor>) -> FsResult<()> {
-        let table = self.table();
+        let descriptor = self.table().get(&fd)?;
 
-        match table.get(&fd)? {
+        match descriptor {
             Descriptor::File(f) => {
                 match f.spawn_blocking(|f| f.sync_all()).await {
                     Ok(()) => Ok(()),
@@ -378,8 +378,8 @@ impl<T: WasiView> HostDescriptor for T {
     }
 
     async fn stat(&mut self, fd: Resource<types::Descriptor>) -> FsResult<types::DescriptorStat> {
-        let table = self.table();
-        match table.get(&fd)? {
+        let descriptor = self.table().get(&fd)?;
+        match descriptor {
             Descriptor::File(f) => {
                 // No permissions check on stat: if opened, allowed to stat it
                 let meta = f.spawn_blocking(|f| f.metadata()).await?;
@@ -492,7 +492,7 @@ impl<T: WasiView> HostDescriptor for T {
         use system_interface::fs::{FdFlags, GetSetFdFlags};
         use types::{DescriptorFlags, OpenFlags};
 
-        let table = self.table_mut();
+        let table = self.table();
         let d = table.get(&fd)?.dir()?;
         if !d.perms.contains(DirPerms::READ) {
             Err(ErrorCode::NotPermitted)?;
@@ -596,7 +596,7 @@ impl<T: WasiView> HostDescriptor for T {
     }
 
     fn drop(&mut self, fd: Resource<types::Descriptor>) -> anyhow::Result<()> {
-        let table = self.table_mut();
+        let table = self.table();
 
         // The Drop will close the file/dir, but if the close syscall
         // blocks the thread, I will face god and walk backwards into hell.
@@ -713,7 +713,7 @@ impl<T: WasiView> HostDescriptor for T {
         let reader = FileInputStream::new(clone, offset);
 
         // Insert the stream view into the table. Trap if the table is full.
-        let index = self.table_mut().push(InputStream::File(reader))?;
+        let index = self.table().push(InputStream::File(reader))?;
 
         Ok(index)
     }
@@ -738,7 +738,7 @@ impl<T: WasiView> HostDescriptor for T {
         let writer: OutputStream = Box::new(writer);
 
         // Insert the stream view into the table. Trap if the table is full.
-        let index = self.table_mut().push(writer)?;
+        let index = self.table().push(writer)?;
 
         Ok(index)
     }
@@ -761,7 +761,7 @@ impl<T: WasiView> HostDescriptor for T {
         let appender: OutputStream = Box::new(appender);
 
         // Insert the stream view into the table. Trap if the table is full.
-        let index = self.table_mut().push(appender)?;
+        let index = self.table().push(appender)?;
 
         Ok(index)
     }
@@ -772,9 +772,10 @@ impl<T: WasiView> HostDescriptor for T {
         b: Resource<types::Descriptor>,
     ) -> anyhow::Result<bool> {
         use cap_fs_ext::MetadataExt;
-        let table = self.table();
-        let meta_a = get_descriptor_metadata(table, a).await?;
-        let meta_b = get_descriptor_metadata(table, b).await?;
+        let descriptor_a = self.table().get(&a)?;
+        let meta_a = get_descriptor_metadata(descriptor_a).await?;
+        let descriptor_b = self.table().get(&b)?;
+        let meta_b = get_descriptor_metadata(descriptor_b).await?;
         if meta_a.dev() == meta_b.dev() && meta_a.ino() == meta_b.ino() {
             // MetadataHashValue does not derive eq, so use a pair of
             // comparisons to check equality:
@@ -796,8 +797,8 @@ impl<T: WasiView> HostDescriptor for T {
         &mut self,
         fd: Resource<types::Descriptor>,
     ) -> FsResult<types::MetadataHashValue> {
-        let table = self.table();
-        let meta = get_descriptor_metadata(table, fd).await?;
+        let descriptor_a = self.table().get(&fd)?;
+        let meta = get_descriptor_metadata(descriptor_a).await?;
         Ok(calculate_metadata_hash(&meta))
     }
     async fn metadata_hash_at(
@@ -834,16 +835,13 @@ impl<T: WasiView> HostDirectoryEntryStream for T {
     }
 
     fn drop(&mut self, stream: Resource<types::DirectoryEntryStream>) -> anyhow::Result<()> {
-        self.table_mut().delete(stream)?;
+        self.table().delete(stream)?;
         Ok(())
     }
 }
 
-async fn get_descriptor_metadata(
-    table: &ResourceTable,
-    fd: Resource<types::Descriptor>,
-) -> FsResult<cap_std::fs::Metadata> {
-    match table.get(&fd)? {
+async fn get_descriptor_metadata(fd: &types::Descriptor) -> FsResult<cap_std::fs::Metadata> {
+    match fd {
         Descriptor::File(f) => {
             // No permissions check on metadata: if opened, allowed to stat it
             Ok(f.spawn_blocking(|f| f.metadata()).await?)
@@ -1057,6 +1055,8 @@ fn mask_file_perms(p: FilePerms, flags: types::DescriptorFlags) -> FilePerms {
 #[cfg(test)]
 mod test {
     use super::*;
+    use wasmtime::component::ResourceTable;
+
     #[test]
     fn table_readdir_works() {
         let mut table = ResourceTable::new();
diff --git a/crates/wasi/src/preview2/host/instance_network.rs b/crates/wasi/src/preview2/host/instance_network.rs
index 382bae913..8af4d0d8a 100644
--- a/crates/wasi/src/preview2/host/instance_network.rs
+++ b/crates/wasi/src/preview2/host/instance_network.rs
@@ -9,7 +9,7 @@ impl<T: WasiView> instance_network::Host for T {
             socket_addr_check: self.ctx().socket_addr_check.clone(),
             allow_ip_name_lookup: self.ctx().allowed_network_uses.ip_name_lookup,
         };
-        let network = self.table_mut().push(network)?;
+        let network = self.table().push(network)?;
         Ok(network)
     }
 }
diff --git a/crates/wasi/src/preview2/host/io.rs b/crates/wasi/src/preview2/host/io.rs
index d04747fc6..b82ccc8ef 100644
--- a/crates/wasi/src/preview2/host/io.rs
+++ b/crates/wasi/src/preview2/host/io.rs
@@ -13,7 +13,7 @@ impl<T: WasiView> streams::Host for T {
         match err {
             StreamError::Closed => Ok(streams::StreamError::Closed),
             StreamError::LastOperationFailed(e) => Ok(streams::StreamError::LastOperationFailed(
-                self.table_mut().push(e)?,
+                self.table().push(e)?,
             )),
             StreamError::Trap(e) => Err(e),
         }
@@ -22,34 +22,34 @@ impl<T: WasiView> streams::Host for T {
 
 impl<T: WasiView> error::HostError for T {
     fn drop(&mut self, err: Resource<streams::Error>) -> anyhow::Result<()> {
-        self.table_mut().delete(err)?;
+        self.table().delete(err)?;
         Ok(())
     }
 
     fn to_debug_string(&mut self, err: Resource<streams::Error>) -> anyhow::Result<String> {
-        Ok(format!("{:?}", self.table_mut().get(&err)?))
+        Ok(format!("{:?}", self.table().get(&err)?))
     }
 }
 
 #[async_trait::async_trait]
 impl<T: WasiView> streams::HostOutputStream for T {
     fn drop(&mut self, stream: Resource<OutputStream>) -> anyhow::Result<()> {
-        self.table_mut().delete(stream)?;
+        self.table().delete(stream)?;
         Ok(())
     }
 
     fn check_write(&mut self, stream: Resource<OutputStream>) -> StreamResult<u64> {
-        let bytes = self.table_mut().get_mut(&stream)?.check_write()?;
+        let bytes = self.table().get_mut(&stream)?.check_write()?;
         Ok(bytes as u64)
     }
 
     fn write(&mut self, stream: Resource<OutputStream>, bytes: Vec<u8>) -> StreamResult<()> {
-        self.table_mut().get_mut(&stream)?.write(bytes.into())?;
+        self.table().get_mut(&stream)?.write(bytes.into())?;
         Ok(())
     }
 
     fn subscribe(&mut self, stream: Resource<OutputStream>) -> anyhow::Result<Resource<Pollable>> {
-        subscribe(self.table_mut(), stream)
+        subscribe(self.table(), stream)
     }
 
     async fn blocking_write_and_flush(
@@ -57,7 +57,7 @@ impl<T: WasiView> streams::HostOutputStream for T {
         stream: Resource<OutputStream>,
         bytes: Vec<u8>,
     ) -> StreamResult<()> {
-        let s = self.table_mut().get_mut(&stream)?;
+        let s = self.table().get_mut(&stream)?;
 
         if bytes.len() > 4096 {
             return Err(StreamError::trap(
@@ -84,7 +84,7 @@ impl<T: WasiView> streams::HostOutputStream for T {
         stream: Resource<OutputStream>,
         len: u64,
     ) -> StreamResult<()> {
-        let s = self.table_mut().get_mut(&stream)?;
+        let s = self.table().get_mut(&stream)?;
 
         if len > 4096 {
             return Err(StreamError::trap(
@@ -107,19 +107,17 @@ impl<T: WasiView> streams::HostOutputStream for T {
     }
 
     fn write_zeroes(&mut self, stream: Resource<OutputStream>, len: u64) -> StreamResult<()> {
-        self.table_mut()
-            .get_mut(&stream)?
-            .write_zeroes(len as usize)?;
+        self.table().get_mut(&stream)?.write_zeroes(len as usize)?;
         Ok(())
     }
 
     fn flush(&mut self, stream: Resource<OutputStream>) -> StreamResult<()> {
-        self.table_mut().get_mut(&stream)?.flush()?;
+        self.table().get_mut(&stream)?.flush()?;
         Ok(())
     }
 
     async fn blocking_flush(&mut self, stream: Resource<OutputStream>) -> StreamResult<()> {
-        let s = self.table_mut().get_mut(&stream)?;
+        let s = self.table().get_mut(&stream)?;
         s.flush()?;
         s.write_ready().await?;
         Ok(())
@@ -134,7 +132,7 @@ impl<T: WasiView> streams::HostOutputStream for T {
         let len = len.try_into().unwrap_or(usize::MAX);
 
         let permit = {
-            let output = self.table_mut().get_mut(&dest)?;
+            let output = self.table().get_mut(&dest)?;
             output.check_write()?
         };
         let len = len.min(permit);
@@ -142,7 +140,7 @@ impl<T: WasiView> streams::HostOutputStream for T {
             return Ok(0);
         }
 
-        let contents = match self.table_mut().get_mut(&src)? {
+        let contents = match self.table().get_mut(&src)? {
             InputStream::Host(h) => h.read(len)?,
             InputStream::File(f) => f.read(len).await?,
         };
@@ -152,7 +150,7 @@ impl<T: WasiView> streams::HostOutputStream for T {
             return Ok(0);
         }
 
-        let output = self.table_mut().get_mut(&dest)?;
+        let output = self.table().get_mut(&dest)?;
         output.write(contents)?;
         Ok(len.try_into().expect("usize can fit in u64"))
     }
@@ -165,9 +163,9 @@ impl<T: WasiView> streams::HostOutputStream for T {
     ) -> StreamResult<u64> {
         use crate::preview2::Subscribe;
 
-        self.table_mut().get_mut(&dest)?.ready().await;
+        self.table().get_mut(&dest)?.ready().await;
 
-        self.table_mut().get_mut(&src)?.ready().await;
+        self.table().get_mut(&src)?.ready().await;
 
         self.splice(dest, src, len).await
     }
@@ -176,13 +174,13 @@ impl<T: WasiView> streams::HostOutputStream for T {
 #[async_trait::async_trait]
 impl<T: WasiView> streams::HostInputStream for T {
     fn drop(&mut self, stream: Resource<InputStream>) -> anyhow::Result<()> {
-        self.table_mut().delete(stream)?;
+        self.table().delete(stream)?;
         Ok(())
     }
 
     async fn read(&mut self, stream: Resource<InputStream>, len: u64) -> StreamResult<Vec<u8>> {
         let len = len.try_into().unwrap_or(usize::MAX);
-        let bytes = match self.table_mut().get_mut(&stream)? {
+        let bytes = match self.table().get_mut(&stream)? {
             InputStream::Host(s) => s.read(len)?,
             InputStream::File(s) => s.read(len).await?,
         };
@@ -195,7 +193,7 @@ impl<T: WasiView> streams::HostInputStream for T {
         stream: Resource<InputStream>,
         len: u64,
     ) -> StreamResult<Vec<u8>> {
-        if let InputStream::Host(s) = self.table_mut().get_mut(&stream)? {
+        if let InputStream::Host(s) = self.table().get_mut(&stream)? {
             s.ready().await;
         }
         self.read(stream, len).await
@@ -203,7 +201,7 @@ impl<T: WasiView> streams::HostInputStream for T {
 
     async fn skip(&mut self, stream: Resource<InputStream>, len: u64) -> StreamResult<u64> {
         let len = len.try_into().unwrap_or(usize::MAX);
-        let written = match self.table_mut().get_mut(&stream)? {
+        let written = match self.table().get_mut(&stream)? {
             InputStream::Host(s) => s.skip(len)?,
             InputStream::File(s) => s.skip(len).await?,
         };
@@ -215,14 +213,14 @@ impl<T: WasiView> streams::HostInputStream for T {
         stream: Resource<InputStream>,
         len: u64,
     ) -> StreamResult<u64> {
-        if let InputStream::Host(s) = self.table_mut().get_mut(&stream)? {
+        if let InputStream::Host(s) = self.table().get_mut(&stream)? {
             s.ready().await;
         }
         self.skip(stream, len).await
     }
 
     fn subscribe(&mut self, stream: Resource<InputStream>) -> anyhow::Result<Resource<Pollable>> {
-        crate::preview2::poll::subscribe(self.table_mut(), stream)
+        crate::preview2::poll::subscribe(self.table(), stream)
     }
 }
 
diff --git a/crates/wasi/src/preview2/host/network.rs b/crates/wasi/src/preview2/host/network.rs
index 31ef2cb61..5b231addf 100644
--- a/crates/wasi/src/preview2/host/network.rs
+++ b/crates/wasi/src/preview2/host/network.rs
@@ -16,7 +16,7 @@ impl<T: WasiView> network::Host for T {
 
 impl<T: WasiView> crate::preview2::bindings::sockets::network::HostNetwork for T {
     fn drop(&mut self, this: Resource<network::Network>) -> Result<(), anyhow::Error> {
-        let table = self.table_mut();
+        let table = self.table();
 
         table.delete(this)?;
 
diff --git a/crates/wasi/src/preview2/host/random.rs b/crates/wasi/src/preview2/host/random.rs
index d2483b39f..583ff8e98 100644
--- a/crates/wasi/src/preview2/host/random.rs
+++ b/crates/wasi/src/preview2/host/random.rs
@@ -4,33 +4,33 @@ use cap_rand::{distributions::Standard, Rng};
 
 impl<T: WasiView> random::Host for T {
     fn get_random_bytes(&mut self, len: u64) -> anyhow::Result<Vec<u8>> {
-        Ok((&mut self.ctx_mut().random)
+        Ok((&mut self.ctx().random)
             .sample_iter(Standard)
             .take(len as usize)
             .collect())
     }
 
     fn get_random_u64(&mut self) -> anyhow::Result<u64> {
-        Ok(self.ctx_mut().random.sample(Standard))
+        Ok(self.ctx().random.sample(Standard))
     }
 }
 
 impl<T: WasiView> insecure::Host for T {
     fn get_insecure_random_bytes(&mut self, len: u64) -> anyhow::Result<Vec<u8>> {
-        Ok((&mut self.ctx_mut().insecure_random)
+        Ok((&mut self.ctx().insecure_random)
             .sample_iter(Standard)
             .take(len as usize)
             .collect())
     }
 
     fn get_insecure_random_u64(&mut self) -> anyhow::Result<u64> {
-        Ok(self.ctx_mut().insecure_random.sample(Standard))
+        Ok(self.ctx().insecure_random.sample(Standard))
     }
 }
 
 impl<T: WasiView> insecure_seed::Host for T {
     fn insecure_seed(&mut self) -> anyhow::Result<(u64, u64)> {
-        let seed: u128 = self.ctx_mut().insecure_random_seed;
+        let seed: u128 = self.ctx().insecure_random_seed;
         Ok((seed as u64, (seed >> 64) as u64))
     }
 }
diff --git a/crates/wasi/src/preview2/host/tcp.rs b/crates/wasi/src/preview2/host/tcp.rs
index 67bd04429..52e2b86b6 100644
--- a/crates/wasi/src/preview2/host/tcp.rs
+++ b/crates/wasi/src/preview2/host/tcp.rs
@@ -29,7 +29,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         local_address: IpSocketAddress,
     ) -> SocketResult<()> {
         self.ctx().allowed_network_uses.check_allowed_tcp()?;
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get(&this)?;
         let network = table.get(&network)?;
         let local_address: SocketAddr = local_address.into();
@@ -77,7 +77,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
     }
 
     fn finish_bind(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<()> {
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get_mut(&this)?;
 
         match socket.tcp_state {
@@ -97,7 +97,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         remote_address: IpSocketAddress,
     ) -> SocketResult<()> {
         self.ctx().allowed_network_uses.check_allowed_tcp()?;
-        let table = self.table_mut();
+        let table = self.table();
         let r = {
             let socket = table.get(&this)?;
             let network = table.get(&network)?;
@@ -154,7 +154,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         &mut self,
         this: Resource<tcp::TcpSocket>,
     ) -> SocketResult<(Resource<InputStream>, Resource<OutputStream>)> {
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get_mut(&this)?;
 
         match socket.tcp_state {
@@ -188,15 +188,15 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
 
         socket.tcp_state = TcpState::Connected;
         let (input, output) = socket.as_split();
-        let input_stream = self.table_mut().push_child(input, &this)?;
-        let output_stream = self.table_mut().push_child(output, &this)?;
+        let input_stream = self.table().push_child(input, &this)?;
+        let output_stream = self.table().push_child(output, &this)?;
 
         Ok((input_stream, output_stream))
     }
 
     fn start_listen(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<()> {
         self.ctx().allowed_network_uses.check_allowed_tcp()?;
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get_mut(&this)?;
 
         match socket.tcp_state {
@@ -219,7 +219,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
     }
 
     fn finish_listen(&mut self, this: Resource<tcp::TcpSocket>) -> SocketResult<()> {
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get_mut(&this)?;
 
         match socket.tcp_state {
@@ -287,9 +287,9 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         let (input, output) = tcp_socket.as_split();
         let output: OutputStream = output;
 
-        let tcp_socket = self.table_mut().push(tcp_socket)?;
-        let input_stream = self.table_mut().push_child(input, &tcp_socket)?;
-        let output_stream = self.table_mut().push_child(output, &tcp_socket)?;
+        let tcp_socket = self.table().push(tcp_socket)?;
+        let input_stream = self.table().push_child(input, &tcp_socket)?;
+        let output_stream = self.table().push_child(output, &tcp_socket)?;
 
         Ok((tcp_socket, input_stream, output_stream))
     }
@@ -361,7 +361,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         const MIN_BACKLOG: i32 = 1;
         const MAX_BACKLOG: i32 = i32::MAX; // OS'es will most likely limit it down even further.
 
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get_mut(&this)?;
 
         if value == 0 {
@@ -426,7 +426,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         this: Resource<tcp::TcpSocket>,
         value: u64,
     ) -> SocketResult<()> {
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get_mut(&this)?;
 
         let duration = Duration::from_nanos(value);
@@ -489,7 +489,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
     }
 
     fn set_hop_limit(&mut self, this: Resource<tcp::TcpSocket>, value: u8) -> SocketResult<()> {
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get_mut(&this)?;
 
         match socket.family {
@@ -518,7 +518,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         this: Resource<tcp::TcpSocket>,
         value: u64,
     ) -> SocketResult<()> {
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get_mut(&this)?;
         let value = value.try_into().unwrap_or(usize::MAX);
 
@@ -545,7 +545,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
         this: Resource<tcp::TcpSocket>,
         value: u64,
     ) -> SocketResult<()> {
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get_mut(&this)?;
         let value = value.try_into().unwrap_or(usize::MAX);
 
@@ -560,7 +560,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
     }
 
     fn subscribe(&mut self, this: Resource<tcp::TcpSocket>) -> anyhow::Result<Resource<Pollable>> {
-        crate::preview2::poll::subscribe(self.table_mut(), this)
+        crate::preview2::poll::subscribe(self.table(), this)
     }
 
     fn shutdown(
@@ -593,7 +593,7 @@ impl<T: WasiView> crate::preview2::host::tcp::tcp::HostTcpSocket for T {
     }
 
     fn drop(&mut self, this: Resource<tcp::TcpSocket>) -> Result<(), anyhow::Error> {
-        let table = self.table_mut();
+        let table = self.table();
 
         // As in the filesystem implementation, we assume closing a socket
         // doesn't block.
diff --git a/crates/wasi/src/preview2/host/tcp_create_socket.rs b/crates/wasi/src/preview2/host/tcp_create_socket.rs
index 3018ef709..90991cb4b 100644
--- a/crates/wasi/src/preview2/host/tcp_create_socket.rs
+++ b/crates/wasi/src/preview2/host/tcp_create_socket.rs
@@ -9,7 +9,7 @@ impl<T: WasiView> tcp_create_socket::Host for T {
         address_family: IpAddressFamily,
     ) -> SocketResult<Resource<TcpSocket>> {
         let socket = TcpSocket::new(address_family.into())?;
-        let socket = self.table_mut().push(socket)?;
+        let socket = self.table().push(socket)?;
         Ok(socket)
     }
 }
diff --git a/crates/wasi/src/preview2/host/udp.rs b/crates/wasi/src/preview2/host/udp.rs
index 8b96a686a..a33340017 100644
--- a/crates/wasi/src/preview2/host/udp.rs
+++ b/crates/wasi/src/preview2/host/udp.rs
@@ -32,7 +32,7 @@ impl<T: WasiView> udp::HostUdpSocket for T {
         local_address: IpSocketAddress,
     ) -> SocketResult<()> {
         self.ctx().allowed_network_uses.check_allowed_udp()?;
-        let table = self.table_mut();
+        let table = self.table();
 
         match table.get(&this)?.udp_state {
             UdpState::Default => {}
@@ -76,7 +76,7 @@ impl<T: WasiView> udp::HostUdpSocket for T {
     }
 
     fn finish_bind(&mut self, this: Resource<udp::UdpSocket>) -> SocketResult<()> {
-        let table = self.table_mut();
+        let table = self.table();
         let socket = table.get_mut(&this)?;
 
         match socket.udp_state {
@@ -96,7 +96,7 @@ impl<T: WasiView> udp::HostUdpSocket for T {
         Resource<udp::IncomingDatagramStream>,
         Resource<udp::OutgoingDatagramStream>,
     )> {
-        let table = self.table_mut();
+        let table = self.table();
 
         let has_active_streams = table
             .iter_children(&this)?
@@ -161,8 +161,8 @@ impl<T: WasiView> udp::HostUdpSocket for T {
         };
 
         Ok((
-            self.table_mut().push_child(incoming_stream, &this)?,
-            self.table_mut().push_child(outgoing_stream, &this)?,
+            self.table().push_child(incoming_stream, &this)?,
+            self.table().push_child(outgoing_stream, &this)?,
         ))
     }
 
@@ -283,11 +283,11 @@ impl<T: WasiView> udp::HostUdpSocket for T {
     }
 
     fn subscribe(&mut self, this: Resource<udp::UdpSocket>) -> anyhow::Result<Resource<Pollable>> {
-        crate::preview2::poll::subscribe(self.table_mut(), this)
+        crate::preview2::poll::subscribe(self.table(), this)
     }
 
     fn drop(&mut self, this: Resource<udp::UdpSocket>) -> Result<(), anyhow::Error> {
-        let table = self.table_mut();
+        let table = self.table();
 
         // As in the filesystem implementation, we assume closing a socket
         // doesn't block.
@@ -363,11 +363,11 @@ impl<T: WasiView> udp::HostIncomingDatagramStream for T {
         &mut self,
         this: Resource<udp::IncomingDatagramStream>,
     ) -> anyhow::Result<Resource<Pollable>> {
-        crate::preview2::poll::subscribe(self.table_mut(), this)
+        crate::preview2::poll::subscribe(self.table(), this)
     }
 
     fn drop(&mut self, this: Resource<udp::IncomingDatagramStream>) -> Result<(), anyhow::Error> {
-        let table = self.table_mut();
+        let table = self.table();
 
         // As in the filesystem implementation, we assume closing a socket
         // doesn't block.
@@ -391,7 +391,7 @@ impl Subscribe for IncomingDatagramStream {
 
 impl<T: WasiView> udp::HostOutgoingDatagramStream for T {
     fn check_send(&mut self, this: Resource<udp::OutgoingDatagramStream>) -> SocketResult<u64> {
-        let table = self.table_mut();
+        let table = self.table();
         let stream = table.get_mut(&this)?;
 
         let permit = match stream.send_state {
@@ -448,7 +448,7 @@ impl<T: WasiView> udp::HostOutgoingDatagramStream for T {
             Ok(())
         }
 
-        let table = self.table_mut();
+        let table = self.table();
         let stream = table.get_mut(&this)?;
 
         match stream.send_state {
@@ -497,11 +497,11 @@ impl<T: WasiView> udp::HostOutgoingDatagramStream for T {
         &mut self,
         this: Resource<udp::OutgoingDatagramStream>,
     ) -> anyhow::Result<Resource<Pollable>> {
-        crate::preview2::poll::subscribe(self.table_mut(), this)
+        crate::preview2::poll::subscribe(self.table(), this)
     }
 
     fn drop(&mut self, this: Resource<udp::OutgoingDatagramStream>) -> Result<(), anyhow::Error> {
-        let table = self.table_mut();
+        let table = self.table();
 
         // As in the filesystem implementation, we assume closing a socket
         // doesn't block.
diff --git a/crates/wasi/src/preview2/host/udp_create_socket.rs b/crates/wasi/src/preview2/host/udp_create_socket.rs
index 23edbddf9..f087eb330 100644
--- a/crates/wasi/src/preview2/host/udp_create_socket.rs
+++ b/crates/wasi/src/preview2/host/udp_create_socket.rs
@@ -9,7 +9,7 @@ impl<T: WasiView> udp_create_socket::Host for T {
         address_family: IpAddressFamily,
     ) -> SocketResult<Resource<UdpSocket>> {
         let socket = UdpSocket::new(address_family.into())?;
-        let socket = self.table_mut().push(socket)?;
+        let socket = self.table().push(socket)?;
         Ok(socket)
     }
 }
diff --git a/crates/wasi/src/preview2/ip_name_lookup.rs b/crates/wasi/src/preview2/ip_name_lookup.rs
index ab69f8f1b..dd9243399 100644
--- a/crates/wasi/src/preview2/ip_name_lookup.rs
+++ b/crates/wasi/src/preview2/ip_name_lookup.rs
@@ -34,7 +34,7 @@ impl<T: WasiView> Host for T {
         }
 
         let task = spawn_blocking(move || blocking_resolve(&host));
-        let resource = self.table_mut().push(ResolveAddressStream::Waiting(task))?;
+        let resource = self.table().push(ResolveAddressStream::Waiting(task))?;
         Ok(resource)
     }
 }
@@ -45,7 +45,7 @@ impl<T: WasiView> HostResolveAddressStream for T {
         &mut self,
         resource: Resource<ResolveAddressStream>,
     ) -> Result<Option<IpAddress>, SocketError> {
-        let stream = self.table_mut().get_mut(&resource)?;
+        let stream: &mut ResolveAddressStream = self.table().get_mut(&resource)?;
         loop {
             match stream {
                 ResolveAddressStream::Waiting(future) => {
@@ -69,11 +69,11 @@ impl<T: WasiView> HostResolveAddressStream for T {
         &mut self,
         resource: Resource<ResolveAddressStream>,
     ) -> Result<Resource<Pollable>> {
-        subscribe(self.table_mut(), resource)
+        subscribe(self.table(), resource)
     }
 
     fn drop(&mut self, resource: Resource<ResolveAddressStream>) -> Result<()> {
-        self.table_mut().delete(resource)?;
+        self.table().delete(resource)?;
         Ok(())
     }
 }
diff --git a/crates/wasi/src/preview2/pipe.rs b/crates/wasi/src/preview2/pipe.rs
index bc8d20b1d..413de2e7f 100644
--- a/crates/wasi/src/preview2/pipe.rs
+++ b/crates/wasi/src/preview2/pipe.rs
@@ -118,7 +118,7 @@ pub struct AsyncReadStream {
 impl AsyncReadStream {
     /// Create a [`AsyncReadStream`]. In order to use the [`HostInputStream`] impl
     /// provided by this struct, the argument must impl [`tokio::io::AsyncRead`].
-    pub fn new<T: tokio::io::AsyncRead + Send + Sync + Unpin + 'static>(mut reader: T) -> Self {
+    pub fn new<T: tokio::io::AsyncRead + Send + Unpin + 'static>(mut reader: T) -> Self {
         let (sender, receiver) = mpsc::channel(1);
         let join_handle = crate::preview2::spawn(async move {
             loop {
@@ -354,7 +354,7 @@ mod test {
         assert_eq!(bs.len(), 0);
     }
 
-    async fn finite_async_reader(contents: &[u8]) -> impl AsyncRead + Send + Sync + 'static {
+    async fn finite_async_reader(contents: &[u8]) -> impl AsyncRead + Send + 'static {
         let (r, mut w) = simplex(contents.len());
         w.write_all(contents).await.unwrap();
         r
diff --git a/crates/wasi/src/preview2/poll.rs b/crates/wasi/src/preview2/poll.rs
index 4fba2a994..64be2420f 100644
--- a/crates/wasi/src/preview2/poll.rs
+++ b/crates/wasi/src/preview2/poll.rs
@@ -9,7 +9,7 @@ use wasmtime::component::{Resource, ResourceTable};
 
 pub type PollableFuture<'a> = Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
 pub type MakeFuture = for<'a> fn(&'a mut dyn Any) -> PollableFuture<'a>;
-pub type ClosureFuture = Box<dyn Fn() -> PollableFuture<'static> + Send + Sync + 'static>;
+pub type ClosureFuture = Box<dyn Fn() -> PollableFuture<'static> + Send + 'static>;
 
 /// A host representation of the `wasi:io/poll.pollable` resource.
 ///
@@ -24,7 +24,7 @@ pub struct Pollable {
 }
 
 #[async_trait::async_trait]
-pub trait Subscribe: Send + Sync + 'static {
+pub trait Subscribe: Send + 'static {
     async fn ready(&mut self);
 }
 
@@ -68,7 +68,7 @@ impl<T: WasiView> poll::Host for T {
     async fn poll(&mut self, pollables: Vec<Resource<Pollable>>) -> Result<Vec<u32>> {
         type ReadylistIndex = u32;
 
-        let table = self.table_mut();
+        let table = self.table();
 
         let mut table_futures: HashMap<u32, (MakeFuture, Vec<ReadylistIndex>)> = HashMap::new();
 
@@ -121,14 +121,14 @@ impl<T: WasiView> poll::Host for T {
 #[async_trait::async_trait]
 impl<T: WasiView> crate::preview2::bindings::io::poll::HostPollable for T {
     async fn block(&mut self, pollable: Resource<Pollable>) -> Result<()> {
-        let table = self.table_mut();
+        let table = self.table();
         let pollable = table.get(&pollable)?;
         let ready = (pollable.make_future)(table.get_any_mut(pollable.index)?);
         ready.await;
         Ok(())
     }
     async fn ready(&mut self, pollable: Resource<Pollable>) -> Result<bool> {
-        let table = self.table_mut();
+        let table = self.table();
         let pollable = table.get(&pollable)?;
         let ready = (pollable.make_future)(table.get_any_mut(pollable.index)?);
         futures::pin_mut!(ready);
@@ -138,9 +138,9 @@ impl<T: WasiView> crate::preview2::bindings::io::poll::HostPollable for T {
         ))
     }
     fn drop(&mut self, pollable: Resource<Pollable>) -> Result<()> {
-        let pollable = self.table_mut().delete(pollable)?;
+        let pollable = self.table().delete(pollable)?;
         if let Some(delete) = pollable.remove_index_on_delete {
-            delete(self.table_mut(), pollable.index)?;
+            delete(self.table(), pollable.index)?;
         }
         Ok(())
     }
diff --git a/crates/wasi/src/preview2/preview0.rs b/crates/wasi/src/preview2/preview0.rs
index ca08eb6cc..71c390cdf 100644
--- a/crates/wasi/src/preview2/preview0.rs
+++ b/crates/wasi/src/preview2/preview0.rs
@@ -4,13 +4,13 @@ use crate::preview2::preview1::wasi_snapshot_preview1::WasiSnapshotPreview1 as S
 use crate::preview2::preview1::WasiPreview1View;
 use wiggle::{GuestError, GuestPtr};
 
-pub fn add_to_linker_async<T: WasiPreview1View + Sync>(
+pub fn add_to_linker_async<T: WasiPreview1View>(
     linker: &mut wasmtime::Linker<T>,
 ) -> anyhow::Result<()> {
     wasi_unstable::add_to_linker(linker, |t| t)
 }
 
-pub fn add_to_linker_sync<T: WasiPreview1View + Sync>(
+pub fn add_to_linker_sync<T: WasiPreview1View>(
     linker: &mut wasmtime::Linker<T>,
 ) -> anyhow::Result<()> {
     sync::add_wasi_unstable_to_linker(linker, |t| t)
diff --git a/crates/wasi/src/preview2/preview1.rs b/crates/wasi/src/preview2/preview1.rs
index 83da22771..a06cd5580 100644
--- a/crates/wasi/src/preview2/preview1.rs
+++ b/crates/wasi/src/preview2/preview1.rs
@@ -23,7 +23,7 @@ use wiggle::{GuestError, GuestPtr, GuestStrCow, GuestType};
 
 #[derive(Debug)]
 struct File {
-    /// The handle to the preview2 descriptor that this file is referencing.
+    /// The handle to the preview2 descriptor of type [`crate::preview2::filesystem::Descriptor::File`].
     fd: Resource<filesystem::Descriptor>,
 
     /// The current-position pointer.
@@ -151,7 +151,14 @@ enum Descriptor {
         stream: Resource<streams::OutputStream>,
         isatty: IsATTY,
     },
-    PreopenDirectory((Resource<filesystem::Descriptor>, String)),
+    /// A fd of type [`crate::preview2::filesystem::Descriptor::Dir`]
+    Directory {
+        fd: Resource<filesystem::Descriptor>,
+        /// The path this directory was preopened as.
+        /// `None` means this directory was opened using `open-at`.
+        preopen_path: Option<String>,
+    },
+    /// A fd of type [`crate::preview2::filesystem::Descriptor::File`]
     File(File),
 }
 
@@ -255,7 +262,10 @@ impl Descriptors {
             .context("failed to call `get-directories`")
             .map_err(types::Error::trap)?
         {
-            descriptors.push(Descriptor::PreopenDirectory((dir.0, dir.1)))?;
+            descriptors.push(Descriptor::Directory {
+                fd: dir.0,
+                preopen_path: Some(dir.1),
+            })?;
         }
         Ok(descriptors)
     }
@@ -300,11 +310,6 @@ impl Descriptors {
         assert!(self.insert(fd, desc).is_none());
         Ok(fd)
     }
-
-    /// Like [Self::push], but for [`File`]
-    fn push_file(&mut self, file: File) -> Result<u32> {
-        self.push(Descriptor::File(file))
-    }
 }
 
 impl WasiPreview1Adapter {
@@ -355,33 +360,27 @@ impl<T: WasiPreview1View + ?Sized> Transaction<'_, T> {
     }
 
     /// Borrows [`File`] corresponding to `fd`
-    /// if it describes a [`Descriptor::File`] of [`crate::preview2::filesystem::File`] type
+    /// if it describes a [`Descriptor::File`]
     fn get_file(&self, fd: types::Fd) -> Result<&File> {
         let fd = fd.into();
         match self.descriptors.get(&fd) {
-            Some(Descriptor::File(file @ File { fd, .. })) => {
-                self.view.table().get(fd)?.file()?;
-                Ok(file)
-            }
+            Some(Descriptor::File(file)) => Ok(file),
             _ => Err(types::Errno::Badf.into()),
         }
     }
 
     /// Mutably borrows [`File`] corresponding to `fd`
-    /// if it describes a [`Descriptor::File`] of [`crate::preview2::filesystem::File`] type
+    /// if it describes a [`Descriptor::File`]
     fn get_file_mut(&mut self, fd: types::Fd) -> Result<&mut File> {
         let fd = fd.into();
         match self.descriptors.get_mut(&fd) {
-            Some(Descriptor::File(file)) => {
-                self.view.table().get(&file.fd)?.file()?;
-                Ok(file)
-            }
+            Some(Descriptor::File(file)) => Ok(file),
             _ => Err(types::Errno::Badf.into()),
         }
     }
 
     /// Borrows [`File`] corresponding to `fd`
-    /// if it describes a [`Descriptor::File`] of [`crate::preview2::filesystem::File`] type.
+    /// if it describes a [`Descriptor::File`]
     ///
     /// # Errors
     ///
@@ -389,11 +388,7 @@ impl<T: WasiPreview1View + ?Sized> Transaction<'_, T> {
     fn get_seekable(&self, fd: types::Fd) -> Result<&File> {
         let fd = fd.into();
         match self.descriptors.get(&fd) {
-            Some(Descriptor::File(file @ File { fd, .. }))
-                if self.view.table().get(fd)?.is_file() =>
-            {
-                Ok(file)
-            }
+            Some(Descriptor::File(file)) => Ok(file),
             Some(
                 Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. },
             ) => {
@@ -408,7 +403,7 @@ impl<T: WasiPreview1View + ?Sized> Transaction<'_, T> {
     fn get_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
         match self.get_descriptor(fd)? {
             Descriptor::File(File { fd, .. }) => Ok(fd.borrowed()),
-            Descriptor::PreopenDirectory((fd, _)) => Ok(fd.borrowed()),
+            Descriptor::Directory { fd, .. } => Ok(fd.borrowed()),
             Descriptor::Stdin { .. } | Descriptor::Stdout { .. } | Descriptor::Stderr { .. } => {
                 Err(types::Errno::Badf.into())
             }
@@ -416,21 +411,17 @@ impl<T: WasiPreview1View + ?Sized> Transaction<'_, T> {
     }
 
     /// Returns [`filesystem::Descriptor`] corresponding to `fd`
-    /// if it describes a [`Descriptor::File`] of [`crate::preview2::filesystem::File`] type
+    /// if it describes a [`Descriptor::File`]
     fn get_file_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
         self.get_file(fd).map(|File { fd, .. }| fd.borrowed())
     }
 
     /// Returns [`filesystem::Descriptor`] corresponding to `fd`
-    /// if it describes a [`Descriptor::File`] or [`Descriptor::PreopenDirectory`]
-    /// of [`crate::preview2::filesystem::Dir`] type
+    /// if it describes a [`Descriptor::Directory`]
     fn get_dir_fd(&self, fd: types::Fd) -> Result<Resource<filesystem::Descriptor>> {
         let fd = fd.into();
         match self.descriptors.get(&fd) {
-            Some(Descriptor::File(File { fd, .. })) if self.view.table().get(fd)?.is_dir() => {
-                Ok(fd.borrowed())
-            }
-            Some(Descriptor::PreopenDirectory((fd, _))) => Ok(fd.borrowed()),
+            Some(Descriptor::Directory { fd, .. }) => Ok(fd.borrowed()),
             _ => Err(types::Errno::Badf.into()),
         }
     }
@@ -499,13 +490,13 @@ trait WasiPreview1ViewExt:
 
 impl<T: WasiPreview1View + preopens::Host> WasiPreview1ViewExt for T {}
 
-pub fn add_to_linker_async<T: WasiPreview1View + Sync>(
+pub fn add_to_linker_async<T: WasiPreview1View>(
     linker: &mut wasmtime::Linker<T>,
 ) -> anyhow::Result<()> {
     wasi_snapshot_preview1::add_to_linker(linker, |t| t)
 }
 
-pub fn add_to_linker_sync<T: WasiPreview1View + Sync>(
+pub fn add_to_linker_sync<T: WasiPreview1View>(
     linker: &mut wasmtime::Linker<T>,
 ) -> anyhow::Result<()> {
     sync::add_wasi_snapshot_preview1_to_linker(linker, |t| t)
@@ -1025,7 +1016,7 @@ impl<
                 streams::HostOutputStream::drop(self, stream)
                     .context("failed to call `drop` on `output-stream`")
             }
-            Descriptor::File(File { fd, .. }) | Descriptor::PreopenDirectory((fd, _)) => {
+            Descriptor::File(File { fd, .. }) | Descriptor::Directory { fd, .. } => {
                 filesystem::HostDescriptor::drop(self, fd).context("failed to call `drop`")
             }
         }
@@ -1067,7 +1058,10 @@ impl<
                     fs_rights_inheriting: fs_rights_base,
                 });
             }
-            Descriptor::PreopenDirectory((_, _)) => {
+            Descriptor::Directory {
+                preopen_path: Some(_),
+                ..
+            } => {
                 // Hard-coded set or rights expected by many userlands:
                 let fs_rights_base = types::Rights::PATH_CREATE_DIRECTORY
                     | types::Rights::PATH_CREATE_FILE
@@ -1108,6 +1102,7 @@ impl<
                     fs_rights_inheriting,
                 });
             }
+            Descriptor::Directory { fd, .. } => (fd.borrowed(), BlockingMode::Blocking, false),
             Descriptor::File(File {
                 fd,
                 blocking_mode,
@@ -1221,7 +1216,7 @@ impl<
                 mtim: 0,
                 ctim: 0,
             }),
-            Descriptor::PreopenDirectory((fd, _)) | Descriptor::File(File { fd, .. }) => {
+            Descriptor::Directory { fd, .. } | Descriptor::File(File { fd, .. }) => {
                 let fd = fd.borrowed();
                 drop(t);
                 let filesystem::DescriptorStat {
@@ -1324,7 +1319,7 @@ impl<
                 blocking_mode,
                 position,
                 ..
-            }) if t.view.table().get(fd)?.is_file() => {
+            }) => {
                 let fd = fd.borrowed();
                 let blocking_mode = *blocking_mode;
                 let position = position.clone();
@@ -1384,7 +1379,7 @@ impl<
         let (buf, read) = match desc {
             Descriptor::File(File {
                 fd, blocking_mode, ..
-            }) if t.view.table().get(fd)?.is_file() => {
+            }) => {
                 let fd = fd.borrowed();
                 let blocking_mode = *blocking_mode;
                 drop(t);
@@ -1433,7 +1428,7 @@ impl<
                 blocking_mode,
                 append,
                 position,
-            }) if t.view.table().get(fd)?.is_file() => {
+            }) => {
                 let fd = fd.borrowed();
                 let fd2 = fd.borrowed();
                 let blocking_mode = *blocking_mode;
@@ -1500,7 +1495,7 @@ impl<
         let n = match desc {
             Descriptor::File(File {
                 fd, blocking_mode, ..
-            }) if t.view.table().get(fd)?.is_file() => {
+            }) => {
                 let fd = fd.borrowed();
                 let blocking_mode = *blocking_mode;
                 drop(t);
@@ -1526,7 +1521,11 @@ impl<
     /// Return a description of the given preopened file descriptor.
     #[instrument(skip(self))]
     fn fd_prestat_get(&mut self, fd: types::Fd) -> Result<types::Prestat, types::Error> {
-        if let Descriptor::PreopenDirectory((_, p)) = self.transact()?.get_descriptor(fd)? {
+        if let Descriptor::Directory {
+            preopen_path: Some(p),
+            ..
+        } = self.transact()?.get_descriptor(fd)?
+        {
             let pr_name_len = p.len().try_into()?;
             return Ok(types::Prestat::Dir(types::PrestatDir { pr_name_len }));
         }
@@ -1542,7 +1541,11 @@ impl<
         path_max_len: types::Size,
     ) -> Result<(), types::Error> {
         let path_max_len = path_max_len.try_into()?;
-        if let Descriptor::PreopenDirectory((_, p)) = self.transact()?.get_descriptor(fd)? {
+        if let Descriptor::Directory {
+            preopen_path: Some(p),
+            ..
+        } = self.transact()?.get_descriptor(fd)?
+        {
             if p.len() > path_max_len {
                 return Err(types::Errno::Nametoolong.into());
             }
@@ -1664,7 +1667,7 @@ impl<
 
         let mut dir = Vec::new();
         for (entry, d_next) in self
-            .table_mut()
+            .table()
             // remove iterator from table and use it directly:
             .delete(stream)?
             .into_iter()
@@ -1897,11 +1900,8 @@ impl<
 
         let t = self.transact()?;
         let dirfd = match t.get_descriptor(dirfd)? {
-            Descriptor::PreopenDirectory((fd, _)) => fd.borrowed(),
-            Descriptor::File(File { fd, .. }) => {
-                t.view.table().get(fd)?.dir()?;
-                fd.borrowed()
-            }
+            Descriptor::Directory { fd, .. } => fd.borrowed(),
+            Descriptor::File(_) => return Err(types::Errno::Notdir.into()),
             _ => return Err(types::Errno::Badf.into()),
         };
         drop(t);
@@ -1913,12 +1913,20 @@ impl<
                     .context("failed to call `open-at`")
                     .unwrap_or_else(types::Error::trap)
             })?;
-        let fd = self.transact()?.descriptors.push_file(File {
-            fd,
-            position: Default::default(),
-            append: fdflags.contains(types::Fdflags::APPEND),
-            blocking_mode: BlockingMode::from_fdflags(&fdflags),
-        })?;
+        let mut t = self.transact()?;
+        let desc = match t.view.table().get(&fd)? {
+            crate::preview2::filesystem::Descriptor::Dir(_) => Descriptor::Directory {
+                fd,
+                preopen_path: None,
+            },
+            crate::preview2::filesystem::Descriptor::File(_) => Descriptor::File(File {
+                fd,
+                position: Default::default(),
+                append: fdflags.contains(types::Fdflags::APPEND),
+                blocking_mode: BlockingMode::from_fdflags(&fdflags),
+            }),
+        };
+        let fd = t.descriptors.push(desc)?;
         Ok(fd.into())
     }
 
@@ -2096,9 +2104,7 @@ impl<
                         let desc = t.get_descriptor(file_descriptor)?;
                         match desc {
                             Descriptor::Stdin { stream, .. } => stream.borrowed(),
-                            Descriptor::File(File { fd, position, .. })
-                                if t.view.table().get(fd)?.is_file() =>
-                            {
+                            Descriptor::File(File { fd, position, .. }) => {
                                 let pos = position.load(Ordering::Relaxed);
                                 let fd = fd.borrowed();
                                 drop(t);
@@ -2130,7 +2136,7 @@ impl<
                                 position,
                                 append,
                                 ..
-                            }) if t.view.table().get(fd)?.is_file() => {
+                            }) => {
                                 let fd = fd.borrowed();
                                 let position = position.clone();
                                 let append = *append;
@@ -2202,9 +2208,7 @@ impl<
                                 nbytes: 1,
                             },
                         },
-                        Descriptor::File(File { fd, position, .. })
-                            if t.view.table().get(fd)?.is_file() =>
-                        {
+                        Descriptor::File(File { fd, position, .. }) => {
                             let fd = fd.borrowed();
                             let position = position.clone();
                             drop(t);
@@ -2247,17 +2251,15 @@ impl<
                                 nbytes: 1,
                             },
                         },
-                        Descriptor::File(File { fd, .. }) if t.view.table().get(fd)?.is_file() => {
-                            types::Event {
-                                userdata: sub.userdata,
-                                error: types::Errno::Success,
-                                type_: types::Eventtype::FdWrite,
-                                fd_readwrite: types::EventFdReadwrite {
-                                    flags: types::Eventrwflags::empty(),
-                                    nbytes: 1,
-                                },
-                            }
-                        }
+                        Descriptor::File(_) => types::Event {
+                            userdata: sub.userdata,
+                            error: types::Errno::Success,
+                            type_: types::Eventtype::FdWrite,
+                            fd_readwrite: types::EventFdReadwrite {
+                                flags: types::Eventrwflags::empty(),
+                                nbytes: 1,
+                            },
+                        },
                         // TODO: Support sockets
                         _ => return Err(types::Errno::Badf.into()),
                     }
diff --git a/crates/wasi/src/preview2/random.rs b/crates/wasi/src/preview2/random.rs
index 9660699e8..4a0b33f29 100644
--- a/crates/wasi/src/preview2/random.rs
+++ b/crates/wasi/src/preview2/random.rs
@@ -51,7 +51,7 @@ mod test {
     }
 }
 
-pub fn thread_rng() -> Box<dyn RngCore + Send + Sync> {
+pub fn thread_rng() -> Box<dyn RngCore + Send> {
     use cap_rand::{Rng, SeedableRng};
     let mut rng = cap_rand::thread_rng(cap_rand::ambient_authority());
     Box::new(cap_rand::rngs::StdRng::from_seed(rng.gen()))
diff --git a/crates/wasi/src/preview2/stdio.rs b/crates/wasi/src/preview2/stdio.rs
index f99a5d2a2..193f7c3dc 100644
--- a/crates/wasi/src/preview2/stdio.rs
+++ b/crates/wasi/src/preview2/stdio.rs
@@ -18,7 +18,7 @@ use wasmtime::component::Resource;
 ///
 /// Built-in implementations are provided for [`Stdin`],
 /// [`pipe::MemoryInputPipe`], and [`pipe::ClosedInputStream`].
-pub trait StdinStream: Send + Sync {
+pub trait StdinStream: Send {
     /// Creates a fresh stream which is reading stdin.
     ///
     /// Note that the returned stream must share state with all other streams
@@ -60,7 +60,7 @@ mod worker_thread_stdin;
 pub use self::worker_thread_stdin::{stdin, Stdin};
 
 /// Similar to [`StdinStream`], except for output.
-pub trait StdoutStream: Send + Sync {
+pub trait StdoutStream: Send {
     /// Returns a fresh new stream which can write to this output stream.
     ///
     /// Note that all output streams should output to the same logical source.
@@ -191,22 +191,22 @@ pub enum IsATTY {
 
 impl<T: WasiView> stdin::Host for T {
     fn get_stdin(&mut self) -> Result<Resource<streams::InputStream>, anyhow::Error> {
-        let stream = self.ctx_mut().stdin.stream();
-        Ok(self.table_mut().push(streams::InputStream::Host(stream))?)
+        let stream = self.ctx().stdin.stream();
+        Ok(self.table().push(streams::InputStream::Host(stream))?)
     }
 }
 
 impl<T: WasiView> stdout::Host for T {
     fn get_stdout(&mut self) -> Result<Resource<streams::OutputStream>, anyhow::Error> {
-        let stream = self.ctx_mut().stdout.stream();
-        Ok(self.table_mut().push(stream)?)
+        let stream = self.ctx().stdout.stream();
+        Ok(self.table().push(stream)?)
     }
 }
 
 impl<T: WasiView> stderr::Host for T {
     fn get_stderr(&mut self) -> Result<Resource<streams::OutputStream>, anyhow::Error> {
-        let stream = self.ctx_mut().stderr.stream();
-        Ok(self.table_mut().push(stream)?)
+        let stream = self.ctx().stderr.stream();
+        Ok(self.table().push(stream)?)
     }
 }
 
@@ -216,21 +216,21 @@ pub struct TerminalOutput;
 impl<T: WasiView> terminal_input::Host for T {}
 impl<T: WasiView> terminal_input::HostTerminalInput for T {
     fn drop(&mut self, r: Resource<TerminalInput>) -> anyhow::Result<()> {
-        self.table_mut().delete(r)?;
+        self.table().delete(r)?;
         Ok(())
     }
 }
 impl<T: WasiView> terminal_output::Host for T {}
 impl<T: WasiView> terminal_output::HostTerminalOutput for T {
     fn drop(&mut self, r: Resource<TerminalOutput>) -> anyhow::Result<()> {
-        self.table_mut().delete(r)?;
+        self.table().delete(r)?;
         Ok(())
     }
 }
 impl<T: WasiView> terminal_stdin::Host for T {
     fn get_terminal_stdin(&mut self) -> anyhow::Result<Option<Resource<TerminalInput>>> {
         if self.ctx().stdin.isatty() {
-            let fd = self.table_mut().push(TerminalInput)?;
+            let fd = self.table().push(TerminalInput)?;
             Ok(Some(fd))
         } else {
             Ok(None)
@@ -240,7 +240,7 @@ impl<T: WasiView> terminal_stdin::Host for T {
 impl<T: WasiView> terminal_stdout::Host for T {
     fn get_terminal_stdout(&mut self) -> anyhow::Result<Option<Resource<TerminalOutput>>> {
         if self.ctx().stdout.isatty() {
-            let fd = self.table_mut().push(TerminalOutput)?;
+            let fd = self.table().push(TerminalOutput)?;
             Ok(Some(fd))
         } else {
             Ok(None)
@@ -250,7 +250,7 @@ impl<T: WasiView> terminal_stdout::Host for T {
 impl<T: WasiView> terminal_stderr::Host for T {
     fn get_terminal_stderr(&mut self) -> anyhow::Result<Option<Resource<TerminalOutput>>> {
         if self.ctx().stderr.isatty() {
-            let fd = self.table_mut().push(TerminalOutput)?;
+            let fd = self.table().push(TerminalOutput)?;
             Ok(Some(fd))
         } else {
             Ok(None)
diff --git a/crates/wasi/src/preview2/write_stream.rs b/crates/wasi/src/preview2/write_stream.rs
index afc2305f0..79f381537 100644
--- a/crates/wasi/src/preview2/write_stream.rs
+++ b/crates/wasi/src/preview2/write_stream.rs
@@ -99,7 +99,7 @@ impl Worker {
         }
         self.write_ready_changed.notify_one();
     }
-    async fn work<T: tokio::io::AsyncWrite + Send + Sync + Unpin + 'static>(&self, mut writer: T) {
+    async fn work<T: tokio::io::AsyncWrite + Send + Unpin + 'static>(&self, mut writer: T) {
         use tokio::io::AsyncWriteExt;
         loop {
             while let Some(job) = self.pop() {
@@ -145,7 +145,7 @@ pub struct AsyncWriteStream {
 impl AsyncWriteStream {
     /// Create a [`AsyncWriteStream`]. In order to use the [`HostOutputStream`] impl
     /// provided by this struct, the argument must impl [`tokio::io::AsyncWrite`].
-    pub fn new<T: tokio::io::AsyncWrite + Send + Sync + Unpin + 'static>(
+    pub fn new<T: tokio::io::AsyncWrite + Send + Unpin + 'static>(
         write_budget: usize,
         writer: T,
     ) -> Self {
diff --git a/crates/wasmtime/src/component/resource_table.rs b/crates/wasmtime/src/component/resource_table.rs
index fe5244d36..4b57ce792 100644
--- a/crates/wasmtime/src/component/resource_table.rs
+++ b/crates/wasmtime/src/component/resource_table.rs
@@ -69,7 +69,7 @@ impl Entry {
 #[derive(Debug)]
 struct TableEntry {
     /// The entry in the table, as a boxed dynamically-typed object
-    entry: Box<dyn Any + Send + Sync>,
+    entry: Box<dyn Any + Send>,
     /// The index of the parent of this entry, if it has one.
     parent: Option<u32>,
     /// The indicies of any children of this entry.
@@ -77,7 +77,7 @@ struct TableEntry {
 }
 
 impl TableEntry {
-    fn new(entry: Box<dyn Any + Send + Sync>, parent: Option<u32>) -> Self {
+    fn new(entry: Box<dyn Any + Send>, parent: Option<u32>) -> Self {
         Self {
             entry,
             parent,
@@ -115,7 +115,7 @@ impl ResourceTable {
     /// `Resource<T>` which can be used to refer to it after it was inserted.
     pub fn push<T>(&mut self, entry: T) -> Result<Resource<T>, ResourceTableError>
     where
-        T: Send + Sync + 'static,
+        T: Send + 'static,
     {
         let idx = self.push_(TableEntry::new(Box::new(entry), None))?;
         Ok(Resource::new_own(idx))
@@ -209,7 +209,7 @@ impl ResourceTable {
         parent: &Resource<U>,
     ) -> Result<Resource<T>, ResourceTableError>
     where
-        T: Send + Sync + 'static,
+        T: Send + 'static,
         U: 'static,
     {
         let parent = parent.rep();
@@ -301,7 +301,7 @@ impl ResourceTable {
     pub fn iter_children<T>(
         &self,
         parent: &Resource<T>,
-    ) -> Result<impl Iterator<Item = &(dyn Any + Send + Sync)>, ResourceTableError>
+    ) -> Result<impl Iterator<Item = &(dyn Any + Send)>, ResourceTableError>
     where
         T: 'static,
     {
diff --git a/examples/externref.c b/examples/externref.c
index 56b2a6f41..f9924bb93 100644
--- a/examples/externref.c
+++ b/examples/externref.c
@@ -60,7 +60,10 @@ int main() {
   fseek(file, 0L, SEEK_SET);
   wasm_byte_vec_t wat;
   wasm_byte_vec_new_uninitialized(&wat, file_size);
-  assert(fread(wat.data, file_size, 1, file) == 1);
+  if (fread(wat.data, file_size, 1, file) != 1) {
+    printf("> Error loading module!\n");
+    return 1;
+  }
   fclose(file);
 
   // Parse the wat into the binary wasm format
diff --git a/examples/hello.c b/examples/hello.c
index 15fa6ea0a..dc7343432 100644
--- a/examples/hello.c
+++ b/examples/hello.c
@@ -63,7 +63,10 @@ int main() {
   fseek(file, 0L, SEEK_SET);
   wasm_byte_vec_t wat;
   wasm_byte_vec_new_uninitialized(&wat, file_size);
-  assert(fread(wat.data, file_size, 1, file) == 1);
+  if (fread(wat.data, file_size, 1, file) != 1) {
+    printf("> Error loading module!\n");
+    return 1;
+  }
   fclose(file);
 
   // Parse the wat into the binary wasm format
diff --git a/examples/interrupt.c b/examples/interrupt.c
index 201c15231..13292f2ca 100644
--- a/examples/interrupt.c
+++ b/examples/interrupt.c
@@ -80,7 +80,10 @@ int main() {
   fseek(file, 0L, SEEK_SET);
   wasm_byte_vec_t wat;
   wasm_byte_vec_new_uninitialized(&wat, file_size);
-  assert(fread(wat.data, file_size, 1, file) == 1);
+  if (fread(wat.data, file_size, 1, file) != 1) {
+    printf("> Error loading module!\n");
+    return 1;
+  }
   fclose(file);
 
   // Parse the wat into the binary wasm format
diff --git a/examples/serialize.c b/examples/serialize.c
index eec6eb126..1d3acc7b3 100644
--- a/examples/serialize.c
+++ b/examples/serialize.c
@@ -56,7 +56,10 @@ int serialize(wasm_byte_vec_t *buffer) {
   fseek(file, 0L, SEEK_SET);
   wasm_byte_vec_t wat;
   wasm_byte_vec_new_uninitialized(&wat, file_size);
-  assert(fread(wat.data, file_size, 1, file) == 1);
+  if (fread(wat.data, file_size, 1, file) != 1) {
+    printf("> Error loading module!\n");
+    return 1;
+  }
   fclose(file);
 
   // Parse the wat into the binary wasm format
diff --git a/examples/wasi-async/main.rs b/examples/wasi-async/main.rs
index 39eafebcd..f99e543a9 100644
--- a/examples/wasi-async/main.rs
+++ b/examples/wasi-async/main.rs
@@ -18,19 +18,11 @@ struct WasiHostCtx {
 }
 
 impl preview2::WasiView for WasiHostCtx {
-    fn table(&self) -> &wasmtime::component::ResourceTable {
-        &self.preview2_table
-    }
-
-    fn table_mut(&mut self) -> &mut wasmtime::component::ResourceTable {
+    fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
         &mut self.preview2_table
     }
 
-    fn ctx(&self) -> &preview2::WasiCtx {
-        &self.preview2_ctx
-    }
-
-    fn ctx_mut(&mut self) -> &mut preview2::WasiCtx {
+    fn ctx(&mut self) -> &mut preview2::WasiCtx {
         &mut self.preview2_ctx
     }
 }
diff --git a/src/commands/run.rs b/src/commands/run.rs
index 81c724bd7..1fdf32a95 100644
--- a/src/commands/run.rs
+++ b/src/commands/run.rs
@@ -11,7 +11,7 @@ use anyhow::{anyhow, bail, Context as _, Error, Result};
 use clap::Parser;
 use std::ffi::OsString;
 use std::path::{Path, PathBuf};
-use std::sync::Arc;
+use std::sync::{Arc, Mutex};
 use std::thread;
 use wasmtime::{Engine, Func, Module, Store, StoreLimits, Val, ValType};
 use wasmtime_wasi::maybe_exit_on_error;
@@ -784,7 +784,8 @@ impl RunCommand {
             builder.allow_udp(enable);
         }
 
-        store.data_mut().preview2_ctx = Some(Arc::new(builder.build()));
+        let ctx = builder.build();
+        store.data_mut().preview2_ctx = Some(Arc::new(Mutex::new(ctx)));
         Ok(())
     }
 }
@@ -792,11 +793,15 @@ impl RunCommand {
 #[derive(Default, Clone)]
 struct Host {
     preview1_ctx: Option<wasmtime_wasi::WasiCtx>,
-    preview2_ctx: Option<Arc<preview2::WasiCtx>>,
+
+    // The Mutex is only needed to satisfy the Sync constraint but we never
+    // actually perform any locking on it as we use Mutex::get_mut for every
+    // access.
+    preview2_ctx: Option<Arc<Mutex<preview2::WasiCtx>>>,
 
     // Resource table for preview2 if the `preview2_ctx` is in use, otherwise
     // "just" an empty table.
-    preview2_table: Arc<wasmtime::component::ResourceTable>,
+    preview2_table: Arc<Mutex<wasmtime::component::ResourceTable>>,
 
     // State necessary for the preview1 implementation of WASI backed by the
     // preview2 host implementation. Only used with the `--preview2` flag right
@@ -815,21 +820,19 @@ struct Host {
 }
 
 impl preview2::WasiView for Host {
-    fn table(&self) -> &wasmtime::component::ResourceTable {
-        &self.preview2_table
-    }
-
-    fn table_mut(&mut self) -> &mut wasmtime::component::ResourceTable {
-        Arc::get_mut(&mut self.preview2_table).expect("preview2 is not compatible with threads")
-    }
-
-    fn ctx(&self) -> &preview2::WasiCtx {
-        self.preview2_ctx.as_ref().unwrap()
+    fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
+        Arc::get_mut(&mut self.preview2_table)
+            .expect("preview2 is not compatible with threads")
+            .get_mut()
+            .unwrap()
     }
 
-    fn ctx_mut(&mut self) -> &mut preview2::WasiCtx {
+    fn ctx(&mut self) -> &mut preview2::WasiCtx {
         let ctx = self.preview2_ctx.as_mut().unwrap();
-        Arc::get_mut(ctx).expect("preview2 is not compatible with threads")
+        Arc::get_mut(ctx)
+            .expect("preview2 is not compatible with threads")
+            .get_mut()
+            .unwrap()
     }
 }
 
@@ -851,7 +854,10 @@ impl wasmtime_wasi_http::types::WasiHttpView for Host {
     }
 
     fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
-        Arc::get_mut(&mut self.preview2_table).expect("preview2 is not compatible with threads")
+        Arc::get_mut(&mut self.preview2_table)
+            .expect("preview2 is not compatible with threads")
+            .get_mut()
+            .unwrap()
     }
 }
 
diff --git a/src/commands/serve.rs b/src/commands/serve.rs
index 9cfc16e30..358abdc81 100644
--- a/src/commands/serve.rs
+++ b/src/commands/serve.rs
@@ -33,19 +33,11 @@ struct Host {
 }
 
 impl WasiView for Host {
-    fn table(&self) -> &wasmtime::component::ResourceTable {
-        &self.table
-    }
-
-    fn table_mut(&mut self) -> &mut wasmtime::component::ResourceTable {
+    fn table(&mut self) -> &mut wasmtime::component::ResourceTable {
         &mut self.table
     }
 
-    fn ctx(&self) -> &WasiCtx {
-        &self.ctx
-    }
-
-    fn ctx_mut(&mut self) -> &mut WasiCtx {
+    fn ctx(&mut self) -> &mut WasiCtx {
         &mut self.ctx
     }
 }


Test patch:
diff --git a/crates/wasi-http/tests/all/http_server.rs b/crates/wasi-http/tests/all/http_server.rs
index 99482c0b1..f1a22abf7 100644
--- a/crates/wasi-http/tests/all/http_server.rs
+++ b/crates/wasi-http/tests/all/http_server.rs
@@ -32,7 +32,7 @@ pub struct Server {
 
 impl Server {
     fn new<F>(
-        run: impl FnOnce(TokioIo<tokio::net::TcpStream>) -> F + Send + Sync + 'static,
+        run: impl FnOnce(TokioIo<tokio::net::TcpStream>) -> F + Send + 'static,
     ) -> Result<Self>
     where
         F: Future<Output = Result<()>>,
diff --git a/crates/wasi-http/tests/all/main.rs b/crates/wasi-http/tests/all/main.rs
index f078a30de..221e97bd3 100644
--- a/crates/wasi-http/tests/all/main.rs
+++ b/crates/wasi-http/tests/all/main.rs
@@ -38,16 +38,10 @@ struct Ctx {
 }
 
 impl WasiView for Ctx {
-    fn table(&self) -> &ResourceTable {
-        &self.table
-    }
-    fn table_mut(&mut self) -> &mut ResourceTable {
+    fn table(&mut self) -> &mut ResourceTable {
         &mut self.table
     }
-    fn ctx(&self) -> &WasiCtx {
-        &self.wasi
-    }
-    fn ctx_mut(&mut self) -> &mut WasiCtx {
+    fn ctx(&mut self) -> &mut WasiCtx {
         &mut self.wasi
     }
 }
@@ -277,9 +271,7 @@ async fn do_wasi_http_hash_all(override_send_request: bool) -> Result<()> {
                         between_bytes_timeout,
                     })
                 });
-                Ok(view
-                    .table()
-                    .push(HostFutureIncomingResponse::Ready(response))?)
+                Ok(WasiHttpView::table(view).push(HostFutureIncomingResponse::Ready(response))?)
             },
         ) as RequestSender)
     } else {
diff --git a/crates/wasi/tests/all/api.rs b/crates/wasi/tests/all/api.rs
index 413713aa1..0da9c7836 100644
--- a/crates/wasi/tests/all/api.rs
+++ b/crates/wasi/tests/all/api.rs
@@ -19,16 +19,10 @@ struct CommandCtx {
 }
 
 impl WasiView for CommandCtx {
-    fn table(&self) -> &ResourceTable {
-        &self.table
-    }
-    fn table_mut(&mut self) -> &mut ResourceTable {
+    fn table(&mut self) -> &mut ResourceTable {
         &mut self.table
     }
-    fn ctx(&self) -> &WasiCtx {
-        &self.wasi
-    }
-    fn ctx_mut(&mut self) -> &mut WasiCtx {
+    fn ctx(&mut self) -> &mut WasiCtx {
         &mut self.wasi
     }
 }
@@ -184,7 +178,7 @@ async fn api_reactor() -> Result<()> {
     // `host` crate for `streams`, not because of `with` in the bindgen macro.
     let writepipe = preview2::pipe::MemoryOutputPipe::new(4096);
     let stream: preview2::OutputStream = Box::new(writepipe.clone());
-    let table_ix = store.data_mut().table_mut().push(stream)?;
+    let table_ix = store.data_mut().table().push(stream)?;
     let r = reactor.call_write_strings_to(&mut store, table_ix).await?;
     assert_eq!(r, Ok(()));
 
diff --git a/crates/wasi/tests/all/main.rs b/crates/wasi/tests/all/main.rs
index eaf9a766e..7be20730f 100644
--- a/crates/wasi/tests/all/main.rs
+++ b/crates/wasi/tests/all/main.rs
@@ -19,16 +19,10 @@ struct Ctx {
 }
 
 impl WasiView for Ctx {
-    fn table(&self) -> &ResourceTable {
-        &self.table
-    }
-    fn table_mut(&mut self) -> &mut ResourceTable {
+    fn table(&mut self) -> &mut ResourceTable {
         &mut self.table
     }
-    fn ctx(&self) -> &WasiCtx {
-        &self.wasi
-    }
-    fn ctx_mut(&mut self) -> &mut WasiCtx {
+    fn ctx(&mut self) -> &mut WasiCtx {
         &mut self.wasi
     }
 }