diff --git a/crates/wash-runtime/src/engine/ctx.rs b/crates/wash-runtime/src/engine/ctx.rs index 6ed5e6b3..91e29464 100644 --- a/crates/wash-runtime/src/engine/ctx.rs +++ b/crates/wash-runtime/src/engine/ctx.rs @@ -4,7 +4,12 @@ //! for wasmtime when executing WebAssembly components. It integrates WASI //! interfaces, HTTP capabilities, and plugin access into a unified context. -use std::{any::Any, collections::HashMap, sync::Arc}; +use std::{ + any::Any, + collections::HashMap, + ops::{Deref, DerefMut}, + sync::Arc, +}; use wasmtime::component::ResourceTable; use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; @@ -12,6 +17,70 @@ use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView}; use crate::plugin::HostPlugin; +/// Shared context for linked components +pub struct SharedCtx { + /// Current active context + pub active_ctx: Ctx, + /// The resource table used to manage resources in the Wasmtime store. + pub table: wasmtime::component::ResourceTable, + /// Contexts for linked components + pub contexts: HashMap, Ctx>, +} + +impl SharedCtx { + pub fn new(context: Ctx) -> Self { + Self { + active_ctx: context, + table: ResourceTable::new(), + contexts: Default::default(), + } + } + + pub fn set_active_ctx(&mut self, id: &Arc) -> anyhow::Result<()> { + if id == &self.active_ctx.component_id { + return Ok(()); + } + + if let Some(ctx) = self.contexts.remove(id) { + let old_ctx = std::mem::replace(&mut self.active_ctx, ctx); + self.contexts.insert(old_ctx.component_id.clone(), old_ctx); + Ok(()) + } else { + Err(anyhow::anyhow!("Context for component {id} not found")) + } + } +} + +impl wasmtime::component::HasData for SharedCtx { + type Data<'a> = ActiveCtx<'a>; +} + +pub fn extract_active_ctx(ctx: &mut SharedCtx) -> ActiveCtx<'_> { + ActiveCtx { + table: &mut ctx.table, + ctx: &mut ctx.active_ctx, + } +} + +pub struct ActiveCtx<'a> { + pub table: &'a mut wasmtime::component::ResourceTable, + pub ctx: &'a mut Ctx, +} + +impl<'a> Deref for ActiveCtx<'a> { + type Target = Ctx; + + fn deref(&self) -> &Self::Target { + self.ctx + } +} + +impl<'a> DerefMut for ActiveCtx<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.ctx + } +} + /// The context for a component store and linker, providing access to implementations of: /// - wasi@0.2 interfaces /// - wasi:http@0.2 interfaces @@ -22,8 +91,6 @@ pub struct Ctx { pub component_id: Arc, /// The unique identifier for the workload this component belongs to pub workload_id: Arc, - /// The resource table used to manage resources in the Wasmtime store. - pub table: ResourceTable, /// The WASI context used to provide WASI functionality to the components using this context. pub ctx: WasiCtx, /// The HTTP context used to provide HTTP functionality to the component. @@ -56,31 +123,30 @@ impl std::fmt::Debug for Ctx { f.debug_struct("Ctx") .field("id", &self.id) .field("workload_id", &self.workload_id.as_ref()) - .field("table", &self.table) .finish() } } // TODO(#103): Do some cleverness to pull up the WasiCtx based on what component is actively executing -impl WasiView for Ctx { +impl WasiView for SharedCtx { fn ctx(&mut self) -> WasiCtxView<'_> { WasiCtxView { - ctx: &mut self.ctx, + ctx: &mut self.active_ctx.ctx, table: &mut self.table, } } } -impl wasmtime_wasi_io::IoView for Ctx { - fn table(&mut self) -> &mut ResourceTable { +impl wasmtime_wasi_io::IoView for SharedCtx { + fn table(&mut self) -> &mut wasmtime_wasi::ResourceTable { &mut self.table } } // Implement WasiHttpView for wasi:http@0.2 -impl WasiHttpView for Ctx { +impl WasiHttpView for SharedCtx { fn ctx(&mut self) -> &mut WasiHttpCtx { - &mut self.http + &mut self.active_ctx.http } fn table(&mut self) -> &mut ResourceTable { @@ -92,8 +158,10 @@ impl WasiHttpView for Ctx { request: hyper::Request, config: wasmtime_wasi_http::types::OutgoingRequestConfig, ) -> wasmtime_wasi_http::HttpResult { - match &self.http_handler { - Some(handler) => handler.outgoing_request(&self.workload_id, request, config), + match &self.active_ctx.http_handler { + Some(handler) => { + handler.outgoing_request(&self.active_ctx.workload_id, request, config) + } None => Err(wasmtime_wasi_http::HttpError::trap(anyhow::anyhow!( "http client not available" ))), @@ -163,7 +231,6 @@ impl CtxBuilder { workload_id: self.workload_id, component_id: self.component_id, http: WasiHttpCtx::new(), - table: ResourceTable::new(), plugins, http_handler: self.http_handler, } diff --git a/crates/wash-runtime/src/engine/mod.rs b/crates/wash-runtime/src/engine/mod.rs index 8bc68ce6..1c484844 100644 --- a/crates/wash-runtime/src/engine/mod.rs +++ b/crates/wash-runtime/src/engine/mod.rs @@ -44,7 +44,7 @@ use wasmtime::PoolingAllocationConfig; use wasmtime::component::{Component, Linker}; use wasmtime_wasi::sockets::loopback; -use crate::engine::ctx::Ctx; +use crate::engine::ctx::SharedCtx; use crate::engine::workload::{UnresolvedWorkload, WorkloadComponent, WorkloadService}; use crate::types::{EmptyDirVolume, HostPathVolume, VolumeType, Workload}; use std::{path::PathBuf, sync::Arc}; @@ -216,7 +216,7 @@ impl Engine { .context("failed to create component from bytes")?; // Create a linker for this component - let mut linker: Linker = Linker::new(&self.inner); + let mut linker: Linker = Linker::new(&self.inner); // Add WASI@0.2 interfaces to the linker wasmtime_wasi::p2::add_to_linker_async(&mut linker) @@ -271,7 +271,7 @@ impl Engine { .context("failed to create component from bytes")?; // Create a linker for this component - let mut linker: Linker = Linker::new(&self.inner); + let mut linker: Linker = Linker::new(&self.inner); // Add WASI@0.2 interfaces to the linker wasmtime_wasi::p2::add_to_linker_async(&mut linker) diff --git a/crates/wash-runtime/src/engine/value.rs b/crates/wash-runtime/src/engine/value.rs index 03462ab8..06178dce 100644 --- a/crates/wash-runtime/src/engine/value.rs +++ b/crates/wash-runtime/src/engine/value.rs @@ -6,9 +6,9 @@ use tracing::trace; use wasmtime::component::Val; use wasmtime::{AsContextMut, StoreContextMut}; -use crate::engine::ctx::Ctx; +use crate::engine::ctx::SharedCtx; -pub(crate) fn lower(store: &mut StoreContextMut<'_, Ctx>, v: &Val) -> anyhow::Result { +pub(crate) fn lower(store: &mut StoreContextMut, v: &Val) -> anyhow::Result { match v { &Val::Bool(v) => Ok(Val::Bool(v)), &Val::S8(v) => Ok(Val::S8(v)), @@ -126,7 +126,7 @@ pub(crate) fn lower(store: &mut StoreContextMut<'_, Ctx>, v: &Val) -> anyhow::Re } } -pub(crate) fn lift(store: &mut StoreContextMut<'_, Ctx>, v: Val) -> anyhow::Result { +pub(crate) fn lift(store: &mut StoreContextMut, v: Val) -> anyhow::Result { match v { Val::Bool(v) => Ok(Val::Bool(v)), Val::S8(v) => Ok(Val::S8(v)), diff --git a/crates/wash-runtime/src/engine/workload.rs b/crates/wash-runtime/src/engine/workload.rs index 59656ab7..0bf1ed56 100644 --- a/crates/wash-runtime/src/engine/workload.rs +++ b/crates/wash-runtime/src/engine/workload.rs @@ -20,7 +20,7 @@ use wasmtime_wasi::{DirPerms, FilePerms, WasiCtxBuilder}; use crate::{ engine::{ - ctx::Ctx, + ctx::{Ctx, SharedCtx}, value::{lift, lower}, }, plugin::HostPlugin, @@ -50,7 +50,7 @@ pub struct WorkloadMetadata { /// The actual wasmtime [`Component`] that can be instantiated component: Component, /// The wasmtime [`Linker`] used to instantiate the component - linker: Linker, + linker: Linker, /// The volume mounts requested by this component volume_mounts: Vec<(PathBuf, VolumeMount)>, /// The local resources requested by this component @@ -59,6 +59,8 @@ pub struct WorkloadMetadata { plugins: Option>>, /// Workload loopback loopback: Arc>, + /// Linked component ids + linked_components: HashSet>, } impl WorkloadMetadata { @@ -88,7 +90,7 @@ impl WorkloadMetadata { } /// Returns a mutable reference to the component's linker. - pub fn linker(&mut self) -> &mut Linker { + pub fn linker(&mut self) -> &mut Linker { &mut self.linker } @@ -228,7 +230,7 @@ impl WorkloadService { workload_name: impl Into>, workload_namespace: impl Into>, component: Component, - linker: Linker, + linker: Linker, volume_mounts: Vec<(PathBuf, VolumeMount)>, local_resources: LocalResources, max_restarts: u64, @@ -246,6 +248,7 @@ impl WorkloadService { local_resources, plugins: None, loopback, + linked_components: Default::default(), }, handle: None, max_restarts, @@ -253,7 +256,7 @@ impl WorkloadService { } /// Pre-instantiate the component to prepare for execution. - pub fn pre_instantiate(&mut self) -> anyhow::Result> { + pub fn pre_instantiate(&mut self) -> anyhow::Result> { let component = self.metadata.component.clone(); let pre = self.metadata.linker.instantiate_pre(&component)?; let command = CommandPre::new(pre)?; @@ -294,7 +297,7 @@ impl WorkloadComponent { workload_namespace: impl Into>, component_name: impl Into>, component: Component, - linker: Linker, + linker: Linker, volume_mounts: Vec<(PathBuf, VolumeMount)>, local_resources: LocalResources, loopback: Arc>, @@ -311,6 +314,7 @@ impl WorkloadComponent { local_resources, plugins: None, loopback, + linked_components: Default::default(), }, name: component_name.into(), // TODO: Implement pooling and instance limits @@ -320,7 +324,7 @@ impl WorkloadComponent { } /// Pre-instantiate the component to prepare for instantiation. - pub fn pre_instantiate(&mut self) -> anyhow::Result> { + pub fn pre_instantiate(&mut self) -> anyhow::Result> { let component = self.metadata.component.clone(); self.metadata.linker.instantiate_pre(&component) } @@ -579,9 +583,17 @@ impl ResolvedWorkload { let component = workload_component.metadata.component.clone(); let linker = &mut workload_component.metadata.linker; - let res = self + let res = match self .resolve_component_imports(&component, linker, interface_map) - .await; + .await + { + Ok(linked_components) => { + workload_component.linked_components = linked_components; + Ok(()) + } + Err(err) => Err(err), + }; + self.components .write() .await @@ -594,9 +606,16 @@ impl ResolvedWorkload { let component = service.metadata.component.clone(); let linker = &mut service.metadata.linker; - let res = self + let res = match self .resolve_component_imports(&component, linker, interface_map) - .await; + .await + { + Ok(linked_components) => { + service.metadata.linked_components = linked_components; + Ok(()) + } + Err(err) => Err(err), + }; self.service = Some(service); @@ -610,14 +629,13 @@ impl ResolvedWorkload { async fn resolve_component_imports( &self, component: &wasmtime::component::Component, - linker: &mut Linker, + linker: &mut Linker, interface_map: &HashMap>, - ) -> anyhow::Result<()> { + ) -> anyhow::Result>> { + let mut linked_components = HashSet::new(); let ty = component.component_type(); let imports: Vec<_> = ty.imports(component.engine()).collect(); - // TODO: some kind of shared import_name -> component registry. need to remove when new store - // store id, instance, import_name. That will keep the instance properly unique let instance: Arc>> = Arc::default(); for (import_name, import_item) in imports.into_iter() { match import_item { @@ -700,6 +718,10 @@ impl ResolvedWorkload { let export_name: Arc = export_name.into(); let pre = pre.clone(); let instance = instance.clone(); + let plugin_component_id = plugin_component.id.clone(); + + linked_components.insert(plugin_component_id.clone()); + linker_instance .func_new_async( &export_name.clone(), @@ -709,10 +731,18 @@ impl ResolvedWorkload { let import_name = import_name.clone(); let export_name = export_name.clone(); let pre = pre.clone(); + let plugin_component_id = plugin_component_id.clone(); let instance = instance.clone(); Box::new(async move { + let prev_id = + store.data().active_ctx.component_id.clone(); + + store + .data_mut() + .set_active_ctx(&plugin_component_id)?; + let existing_instance = instance.read().await; - let store_id = store.data().id.clone(); + let store_id = store.data().active_ctx.id.clone(); let instance = if let Some((id, instance)) = existing_instance.clone() && id == store_id @@ -793,6 +823,9 @@ impl ResolvedWorkload { func.post_return_async(&mut store) .await .context("failed to execute post-return")?; + + store.data_mut().set_active_ctx(&prev_id)?; + Ok(()) }) }, @@ -875,7 +908,7 @@ impl ResolvedWorkload { } } - Ok(()) + Ok(linked_components) } /// Gets the unique identifier of the workload @@ -900,21 +933,20 @@ impl ResolvedWorkload { } /// Helper to create a new wasmtime Store for a given component in the workload. - pub async fn new_store(&self, component_id: &str) -> anyhow::Result> { + async fn new_ctx(&self, component_id: &str) -> anyhow::Result { let components = self.components.read().await; let component = components .get(component_id) .context("component ID not found in workload")?; - self.new_store_from_metadata(&component.metadata, false) - .await + self.new_ctx_from_metadata(&component.metadata, false).await } /// Creates a new wasmtime Store from the given workload metadata. - pub async fn new_store_from_metadata( + async fn new_ctx_from_metadata( &self, metadata: &WorkloadMetadata, is_service: bool, - ) -> anyhow::Result> { + ) -> anyhow::Result { let components = self.components.read().await; // TODO: Consider stderr/stdout buffering + logging @@ -973,7 +1005,39 @@ impl ResolvedWorkload { ctx_builder = ctx_builder.with_plugins(plugins.clone()); } - let store = wasmtime::Store::new(metadata.engine(), ctx_builder.build()); + Ok(ctx_builder.build()) + } + + /// Helper to create a new wasmtime Store for multiple components and set active given component in the workload. + pub async fn new_store( + &self, + component_id: &str, + ) -> anyhow::Result> { + let components = self.components.read().await; + let component = components + .get(component_id) + .context("component ID not found in workload")?; + self.new_store_from_metadata(&component.metadata, false) + .await + } + + /// Creates a new wasmtime Store for multiple components from the given workload metadata. + pub async fn new_store_from_metadata( + &self, + metadata: &WorkloadMetadata, + is_service: bool, + ) -> anyhow::Result> { + let active_ctx = self.new_ctx_from_metadata(metadata, is_service).await?; + let mut shared_ctx = SharedCtx::new(active_ctx); + + for linked_component_id in metadata.linked_components.iter() { + let linked_component_ctx = self.new_ctx(linked_component_id).await?; + shared_ctx + .contexts + .insert(linked_component_id.clone(), linked_component_ctx); + } + + let store = wasmtime::Store::new(metadata.engine(), shared_ctx); Ok(store) } @@ -981,7 +1045,7 @@ impl ResolvedWorkload { pub async fn instantiate_pre( &self, component_id: &str, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let mut components = self.components.write().await; let component = components .get_mut(component_id) diff --git a/crates/wash-runtime/src/host/http.rs b/crates/wash-runtime/src/host/http.rs index 44a0e0cb..28aa4cd2 100644 --- a/crates/wash-runtime/src/host/http.rs +++ b/crates/wash-runtime/src/host/http.rs @@ -25,7 +25,7 @@ use std::{ sync::Arc, }; -use crate::engine::ctx::Ctx; +use crate::engine::ctx::SharedCtx; use crate::engine::workload::ResolvedWorkload; use crate::wit::WitInterface; use anyhow::{Context, ensure}; @@ -290,7 +290,7 @@ impl HostHandler for NullServer { /// A map from host header to resolved workload handles and their associated component id pub type WorkloadHandles = - Arc, String)>>>; + Arc, String)>>>; /// HTTP server plugin that handles incoming HTTP requests for WebAssembly components. /// @@ -598,7 +598,7 @@ async fn handle_http_request( /// Invoke the component handler for the given workload async fn invoke_component_handler( workload_handle: ResolvedWorkload, - instance_pre: InstancePre, + instance_pre: InstancePre, component_id: &str, req: hyper::Request, ) -> anyhow::Result> { @@ -610,8 +610,8 @@ async fn invoke_component_handler( /// Handle a component request using WASI HTTP (copied from wash/crates/src/cli/dev.rs) pub async fn handle_component_request( - mut store: Store, - pre: InstancePre, + mut store: Store, + pre: InstancePre, req: hyper::Request, ) -> anyhow::Result> { let (sender, receiver) = tokio::sync::oneshot::channel(); diff --git a/crates/wash-runtime/src/plugin/wasi_blobstore.rs b/crates/wash-runtime/src/plugin/wasi_blobstore.rs index 989ed4a9..49edcdaf 100644 --- a/crates/wash-runtime/src/plugin/wasi_blobstore.rs +++ b/crates/wash-runtime/src/plugin/wasi_blobstore.rs @@ -11,15 +11,17 @@ use std::{ const WASI_BLOBSTORE_ID: &str = "wasi-blobstore"; use tokio::sync::RwLock; -use wasmtime::component::{HasSelf, Resource}; +use wasmtime::component::Resource; use wasmtime_wasi::p2::{ InputStream, OutputStream, pipe::{MemoryInputPipe, MemoryOutputPipe}, }; use crate::{ - engine::ctx::Ctx, - engine::workload::WorkloadComponent, + engine::{ + ctx::{ActiveCtx, SharedCtx, extract_active_ctx}, + workload::WorkloadComponent, + }, plugin::HostPlugin, wit::{WitInterface, WitWorld}, }; @@ -108,7 +110,7 @@ impl WasiBlobstore { } // Implementation for the main blobstore interface -impl bindings::wasi::blobstore::blobstore::Host for Ctx { +impl<'a> bindings::wasi::blobstore::blobstore::Host for ActiveCtx<'a> { async fn create_container( &mut self, name: ContainerName, @@ -269,7 +271,7 @@ impl bindings::wasi::blobstore::blobstore::Host for Ctx { } // Resource host trait implementations - these handle the lifecycle of each resource type -impl bindings::wasi::blobstore::container::HostContainer for Ctx { +impl<'a> bindings::wasi::blobstore::container::HostContainer for ActiveCtx<'a> { async fn name( &mut self, container: Resource, @@ -591,7 +593,7 @@ impl bindings::wasi::blobstore::container::HostContainer for Ctx { } } -impl bindings::wasi::blobstore::container::HostStreamObjectNames for Ctx { +impl<'a> bindings::wasi::blobstore::container::HostStreamObjectNames for ActiveCtx<'a> { async fn read_stream_object_names( &mut self, stream: Resource, @@ -649,7 +651,7 @@ impl bindings::wasi::blobstore::container::HostStreamObjectNames for Ctx { } } -impl bindings::wasi::blobstore::types::HostOutgoingValue for Ctx { +impl<'a> bindings::wasi::blobstore::types::HostOutgoingValue for ActiveCtx<'a> { async fn new_outgoing_value(&mut self) -> anyhow::Result> { tracing::debug!(workload_id = self.id, "Creating new OutgoingValue"); @@ -699,7 +701,7 @@ impl bindings::wasi::blobstore::types::HostOutgoingValue for Ctx { let handle = match self.table.get_mut(&outgoing_value) { Ok(h) => { tracing::debug!( - workload_id = self.id, + workload_id = self.ctx.id, "Successfully retrieved OutgoingValueHandle from table" ); h @@ -715,7 +717,7 @@ impl bindings::wasi::blobstore::types::HostOutgoingValue for Ctx { }; tracing::debug!( - workload_id = self.id, + workload_id = self.ctx.id, "Creating boxed OutputStream from pipe" ); @@ -836,7 +838,7 @@ impl bindings::wasi::blobstore::types::HostOutgoingValue for Ctx { } } -impl bindings::wasi::blobstore::types::HostIncomingValue for Ctx { +impl<'a> bindings::wasi::blobstore::types::HostIncomingValue for ActiveCtx<'a> { async fn incoming_value_consume_sync( &mut self, incoming_value: Resource, @@ -898,10 +900,10 @@ impl bindings::wasi::blobstore::types::HostIncomingValue for Ctx { // traits are sealed and can only be implemented on &mut _T types. // Implement the main types Host trait that combines all resource types -impl bindings::wasi::blobstore::types::Host for Ctx {} +impl<'a> bindings::wasi::blobstore::types::Host for ActiveCtx<'a> {} // Implement the main container Host trait that combines all resource types -impl bindings::wasi::blobstore::container::Host for Ctx {} +impl<'a> bindings::wasi::blobstore::container::Host for ActiveCtx<'a> {} #[async_trait::async_trait] impl HostPlugin for WasiBlobstore { @@ -944,9 +946,18 @@ impl HostPlugin for WasiBlobstore { ); let linker = workload_handle.linker(); - bindings::wasi::blobstore::blobstore::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; - bindings::wasi::blobstore::container::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; - bindings::wasi::blobstore::types::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; + bindings::wasi::blobstore::blobstore::add_to_linker::<_, SharedCtx>( + linker, + extract_active_ctx, + )?; + bindings::wasi::blobstore::container::add_to_linker::<_, SharedCtx>( + linker, + extract_active_ctx, + )?; + bindings::wasi::blobstore::types::add_to_linker::<_, SharedCtx>( + linker, + extract_active_ctx, + )?; let id = workload_handle.workload_id(); diff --git a/crates/wash-runtime/src/plugin/wasi_config.rs b/crates/wash-runtime/src/plugin/wasi_config.rs index 5f97de3e..0ea30ae2 100644 --- a/crates/wash-runtime/src/plugin/wasi_config.rs +++ b/crates/wash-runtime/src/plugin/wasi_config.rs @@ -22,10 +22,12 @@ use std::{ sync::Arc, }; use tokio::sync::RwLock; -use wasmtime::component::HasSelf; use crate::{ - engine::{ctx::Ctx, workload::WorkloadComponent}, + engine::{ + ctx::{ActiveCtx, SharedCtx, extract_active_ctx}, + workload::WorkloadComponent, + }, plugin::HostPlugin, wit::{WitInterface, WitWorld}, }; @@ -54,7 +56,7 @@ pub struct WasiConfig { config: Arc>, } -impl Host for Ctx { +impl<'a> Host for ActiveCtx<'a> { async fn get( &mut self, key: String, @@ -114,9 +116,9 @@ impl HostPlugin for WasiConfig { }; // Add `wasi:config/store` to the workload's linker - bindings::wasi::config::store::add_to_linker::<_, HasSelf>( + bindings::wasi::config::store::add_to_linker::<_, SharedCtx>( component_handle.linker(), - |ctx| ctx, + extract_active_ctx, )?; // Store the configuration for lookups later diff --git a/crates/wash-runtime/src/plugin/wasi_keyvalue.rs b/crates/wash-runtime/src/plugin/wasi_keyvalue.rs index fdafd59b..b3e46a25 100644 --- a/crates/wash-runtime/src/plugin/wasi_keyvalue.rs +++ b/crates/wash-runtime/src/plugin/wasi_keyvalue.rs @@ -10,10 +10,13 @@ use std::{ const WASI_KEYVALUE_ID: &str = "wasi-keyvalue"; use tokio::sync::RwLock; -use wasmtime::component::{HasSelf, Resource}; +use wasmtime::component::Resource; use crate::{ - engine::{ctx::Ctx, workload::WorkloadComponent}, + engine::{ + ctx::{ActiveCtx, SharedCtx, extract_active_ctx}, + workload::WorkloadComponent, + }, plugin::HostPlugin, wit::{WitInterface, WitWorld}, }; @@ -64,7 +67,7 @@ impl WasiKeyvalue { } // Implementation for the store interface -impl bindings::wasi::keyvalue::store::Host for Ctx { +impl<'a> bindings::wasi::keyvalue::store::Host for ActiveCtx<'a> { async fn open( &mut self, identifier: String, @@ -94,7 +97,7 @@ impl bindings::wasi::keyvalue::store::Host for Ctx { } // Resource host trait implementations for bucket -impl bindings::wasi::keyvalue::store::HostBucket for Ctx { +impl<'a> bindings::wasi::keyvalue::store::HostBucket for ActiveCtx<'a> { async fn get( &mut self, bucket: Resource, @@ -269,7 +272,7 @@ impl bindings::wasi::keyvalue::store::HostBucket for Ctx { } // Implementation for the atomics interface -impl bindings::wasi::keyvalue::atomics::Host for Ctx { +impl<'a> bindings::wasi::keyvalue::atomics::Host for ActiveCtx<'a> { async fn increment( &mut self, bucket: Resource, @@ -320,7 +323,7 @@ impl bindings::wasi::keyvalue::atomics::Host for Ctx { } // Implementation for the batch interface -impl bindings::wasi::keyvalue::batch::Host for Ctx { +impl<'a> bindings::wasi::keyvalue::batch::Host for ActiveCtx<'a> { async fn get_many( &mut self, bucket: Resource, @@ -458,9 +461,12 @@ impl HostPlugin for WasiKeyvalue { ); let linker = component.linker(); - bindings::wasi::keyvalue::store::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; - bindings::wasi::keyvalue::atomics::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; - bindings::wasi::keyvalue::batch::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; + bindings::wasi::keyvalue::store::add_to_linker::<_, SharedCtx>(linker, extract_active_ctx)?; + bindings::wasi::keyvalue::atomics::add_to_linker::<_, SharedCtx>( + linker, + extract_active_ctx, + )?; + bindings::wasi::keyvalue::batch::add_to_linker::<_, SharedCtx>(linker, extract_active_ctx)?; let id = component.workload_id(); tracing::debug!( diff --git a/crates/wash-runtime/src/plugin/wasi_logging.rs b/crates/wash-runtime/src/plugin/wasi_logging.rs index b5a51bcb..76acf056 100644 --- a/crates/wash-runtime/src/plugin/wasi_logging.rs +++ b/crates/wash-runtime/src/plugin/wasi_logging.rs @@ -28,12 +28,14 @@ use std::collections::HashSet; use anyhow::bail; -use wasmtime::component::HasSelf; const WASI_LOGGING_ID: &str = "wasi-logging"; use crate::{ - engine::{ctx::Ctx, workload::WorkloadComponent}, + engine::{ + ctx::{ActiveCtx, SharedCtx, extract_active_ctx}, + workload::WorkloadComponent, + }, plugin::{HostPlugin, wasi_logging::bindings::wasi::logging::logging::Level}, wit::{WitInterface, WitWorld}, }; @@ -52,7 +54,7 @@ mod bindings { /// processed and routed by the host's logging system. pub struct WasiLogging; -impl bindings::wasi::logging::logging::Host for Ctx { +impl<'a> bindings::wasi::logging::logging::Host for ActiveCtx<'a> { async fn log(&mut self, level: Level, context: String, message: String) -> anyhow::Result<()> { match level { Level::Critical => tracing::error!(id = &self.id, context, "{message}"), @@ -101,9 +103,9 @@ impl HostPlugin for WasiLogging { } // Add `wasi:logging/logging` to the workload's linker - bindings::wasi::logging::logging::add_to_linker::<_, HasSelf>( + bindings::wasi::logging::logging::add_to_linker::<_, SharedCtx>( workload_handle.linker(), - |ctx| ctx, + extract_active_ctx, )?; Ok(()) diff --git a/crates/wash-runtime/src/plugin/wasi_webgpu.rs b/crates/wash-runtime/src/plugin/wasi_webgpu.rs index 85186c15..03bfb35f 100644 --- a/crates/wash-runtime/src/plugin/wasi_webgpu.rs +++ b/crates/wash-runtime/src/plugin/wasi_webgpu.rs @@ -8,7 +8,7 @@ use std::{collections::HashSet, sync::Arc}; const WASI_WEBGPU_ID: &str = "wasi-webgpu"; use crate::{ - engine::{ctx::Ctx, workload::WorkloadComponent}, + engine::{ctx::SharedCtx, workload::WorkloadComponent}, plugin::HostPlugin, wit::{WitInterface, WitWorld}, }; @@ -65,7 +65,7 @@ impl Default for WasiWebGpu { } } -impl wasi_graphics_context_wasmtime::WasiGraphicsContextView for Ctx {} +impl wasi_graphics_context_wasmtime::WasiGraphicsContextView for SharedCtx {} struct UiThreadSpawner; impl wasi_webgpu_wasmtime::MainThreadSpawner for UiThreadSpawner { @@ -78,9 +78,12 @@ impl wasi_webgpu_wasmtime::MainThreadSpawner for UiThreadSpawner { } } -impl wasi_webgpu_wasmtime::WasiWebGpuView for Ctx { +impl wasi_webgpu_wasmtime::WasiWebGpuView for SharedCtx { fn instance(&self) -> Arc { - let plugin = self.get_plugin::(WASI_WEBGPU_ID).unwrap(); + let plugin = self + .active_ctx + .get_plugin::(WASI_WEBGPU_ID) + .unwrap(); Arc::clone(&plugin.gpu) } diff --git a/crates/wash-runtime/src/washlet/plugins/wasi_blobstore.rs b/crates/wash-runtime/src/washlet/plugins/wasi_blobstore.rs index aeb39a6e..0619102a 100644 --- a/crates/wash-runtime/src/washlet/plugins/wasi_blobstore.rs +++ b/crates/wash-runtime/src/washlet/plugins/wasi_blobstore.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use std::sync::Arc; -use crate::engine::ctx::Ctx; +use crate::engine::ctx::{ActiveCtx, SharedCtx, extract_active_ctx}; use crate::engine::workload::WorkloadComponent; use crate::plugin::HostPlugin; use crate::washlet::plugins::WorkloadTracker; @@ -11,7 +11,7 @@ use async_nats::jetstream::object_store::{self, List, Object, ObjectStore}; use futures::StreamExt; use tokio::io::AsyncReadExt; use tokio::sync::RwLock; -use wasmtime::component::{HasSelf, Resource}; +use wasmtime::component::Resource; use wasmtime_wasi::p2::pipe::{AsyncReadStream, AsyncWriteStream}; use wasmtime_wasi::p2::{InputStream, OutputStream}; @@ -122,7 +122,7 @@ impl WasiBlobstore { } // Implementation for the main blobstore interface -impl bindings::wasi::blobstore::blobstore::Host for Ctx { +impl<'a> bindings::wasi::blobstore::blobstore::Host for ActiveCtx<'a> { async fn create_container( &mut self, name: ContainerName, @@ -330,7 +330,7 @@ impl bindings::wasi::blobstore::blobstore::Host for Ctx { } } -impl bindings::wasi::blobstore::container::HostContainer for Ctx { +impl<'a> bindings::wasi::blobstore::container::HostContainer for ActiveCtx<'a> { async fn name( &mut self, container: Resource, @@ -581,7 +581,7 @@ impl bindings::wasi::blobstore::container::HostContainer for Ctx { } } -impl bindings::wasi::blobstore::container::HostStreamObjectNames for Ctx { +impl<'a> bindings::wasi::blobstore::container::HostStreamObjectNames for ActiveCtx<'a> { async fn read_stream_object_names( &mut self, stream: Resource, @@ -642,7 +642,7 @@ impl bindings::wasi::blobstore::container::HostStreamObjectNames for Ctx { } } -impl bindings::wasi::blobstore::types::HostOutgoingValue for Ctx { +impl<'a> bindings::wasi::blobstore::types::HostOutgoingValue for ActiveCtx<'a> { async fn new_outgoing_value(&mut self) -> anyhow::Result> { let temp_file = tempfile::Builder::new() .tempfile() @@ -720,7 +720,7 @@ impl bindings::wasi::blobstore::types::HostOutgoingValue for Ctx { } } -impl bindings::wasi::blobstore::types::HostIncomingValue for Ctx { +impl<'a> bindings::wasi::blobstore::types::HostIncomingValue for ActiveCtx<'a> { async fn incoming_value_consume_sync( &mut self, incoming_value: Resource, @@ -765,10 +765,10 @@ impl bindings::wasi::blobstore::types::HostIncomingValue for Ctx { } // Implement the main types Host trait that combines all resource types -impl bindings::wasi::blobstore::types::Host for Ctx {} +impl<'a> bindings::wasi::blobstore::types::Host for ActiveCtx<'a> {} // Implement the main container Host trait that combines all resource types -impl bindings::wasi::blobstore::container::Host for Ctx {} +impl<'a> bindings::wasi::blobstore::container::Host for ActiveCtx<'a> {} #[async_trait::async_trait] impl HostPlugin for WasiBlobstore { @@ -803,9 +803,18 @@ impl HostPlugin for WasiBlobstore { ); let linker = component_handle.linker(); - bindings::wasi::blobstore::blobstore::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; - bindings::wasi::blobstore::container::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; - bindings::wasi::blobstore::types::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; + bindings::wasi::blobstore::blobstore::add_to_linker::<_, SharedCtx>( + linker, + extract_active_ctx, + )?; + bindings::wasi::blobstore::container::add_to_linker::<_, SharedCtx>( + linker, + extract_active_ctx, + )?; + bindings::wasi::blobstore::types::add_to_linker::<_, SharedCtx>( + linker, + extract_active_ctx, + )?; Ok(()) } diff --git a/crates/wash-runtime/src/washlet/plugins/wasi_config.rs b/crates/wash-runtime/src/washlet/plugins/wasi_config.rs index 1570a5e0..f7e662af 100644 --- a/crates/wash-runtime/src/washlet/plugins/wasi_config.rs +++ b/crates/wash-runtime/src/washlet/plugins/wasi_config.rs @@ -6,11 +6,10 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; use tokio::sync::RwLock; -use wasmtime::component::HasSelf; const PLUGIN_WASI_CONFIG_ID: &str = "wasi-config"; -use crate::engine::ctx::Ctx; +use crate::engine::ctx::{ActiveCtx, SharedCtx, extract_active_ctx}; use crate::engine::workload::WorkloadComponent; use crate::plugin::HostPlugin; use crate::wit::{WitInterface, WitWorld}; @@ -35,7 +34,7 @@ pub struct WasiConfig { config: Arc>>>, } -impl Host for Ctx { +impl<'a> Host for ActiveCtx<'a> { async fn get(&mut self, key: String) -> anyhow::Result, ConfigError>> { let Some(plugin) = self.get_plugin::(PLUGIN_WASI_CONFIG_ID) else { return Ok(Ok(None)); @@ -90,9 +89,9 @@ impl HostPlugin for WasiConfig { }; // Add `wasi:config/store` to the workload's linker - bindings::wasi::config::store::add_to_linker::<_, HasSelf>( + bindings::wasi::config::store::add_to_linker::<_, SharedCtx>( component_handle.linker(), - |ctx| ctx, + extract_active_ctx, )?; // Store the configuration for lookups later diff --git a/crates/wash-runtime/src/washlet/plugins/wasi_keyvalue.rs b/crates/wash-runtime/src/washlet/plugins/wasi_keyvalue.rs index 6f1eef60..9afd2624 100644 --- a/crates/wash-runtime/src/washlet/plugins/wasi_keyvalue.rs +++ b/crates/wash-runtime/src/washlet/plugins/wasi_keyvalue.rs @@ -10,12 +10,12 @@ use std::sync::Arc; use bytes::{Buf, Bytes}; const PLUGIN_KEYVALUE_ID: &str = "wasi-keyvalue"; -use crate::engine::ctx::Ctx; +use crate::engine::ctx::{ActiveCtx, SharedCtx, extract_active_ctx}; use crate::engine::workload::WorkloadComponent; use crate::plugin::HostPlugin; use crate::wit::{WitInterface, WitWorld}; use futures::StreamExt; -use wasmtime::component::{HasSelf, Resource}; +use wasmtime::component::Resource; const LIST_KEYS_BATCH_SIZE: usize = 1000; @@ -77,7 +77,7 @@ impl WasiKeyvalue { } // Implementation for the store interface -impl bindings::wasi::keyvalue::store::Host for Ctx { +impl<'a> bindings::wasi::keyvalue::store::Host for ActiveCtx<'a> { async fn open( &mut self, identifier: String, @@ -110,7 +110,7 @@ impl bindings::wasi::keyvalue::store::Host for Ctx { } // Resource host trait implementations for bucket -impl bindings::wasi::keyvalue::store::HostBucket for Ctx { +impl<'a> bindings::wasi::keyvalue::store::HostBucket for ActiveCtx<'a> { async fn get( &mut self, bucket: Resource, @@ -268,7 +268,7 @@ impl bindings::wasi::keyvalue::store::HostBucket for Ctx { } // Implementation for the atomics interface -impl bindings::wasi::keyvalue::atomics::Host for Ctx { +impl<'a> bindings::wasi::keyvalue::atomics::Host for ActiveCtx<'a> { async fn increment( &mut self, bucket: Resource, @@ -329,7 +329,7 @@ impl bindings::wasi::keyvalue::atomics::Host for Ctx { } // Implementation for the batch interface -impl bindings::wasi::keyvalue::batch::Host for Ctx { +impl<'a> bindings::wasi::keyvalue::batch::Host for ActiveCtx<'a> { async fn get_many( &mut self, bucket: Resource, @@ -485,9 +485,12 @@ impl HostPlugin for WasiKeyvalue { ); let linker = component.linker(); - bindings::wasi::keyvalue::store::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; - bindings::wasi::keyvalue::atomics::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; - bindings::wasi::keyvalue::batch::add_to_linker::<_, HasSelf>(linker, |ctx| ctx)?; + bindings::wasi::keyvalue::store::add_to_linker::<_, SharedCtx>(linker, extract_active_ctx)?; + bindings::wasi::keyvalue::atomics::add_to_linker::<_, SharedCtx>( + linker, + extract_active_ctx, + )?; + bindings::wasi::keyvalue::batch::add_to_linker::<_, SharedCtx>(linker, extract_active_ctx)?; let id = component.id(); tracing::debug!( diff --git a/crates/wash-runtime/src/washlet/plugins/wasi_logging.rs b/crates/wash-runtime/src/washlet/plugins/wasi_logging.rs index 97506f6b..dfb41610 100644 --- a/crates/wash-runtime/src/washlet/plugins/wasi_logging.rs +++ b/crates/wash-runtime/src/washlet/plugins/wasi_logging.rs @@ -8,7 +8,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use crate::engine::ctx::Ctx; +use crate::engine::ctx::{ActiveCtx, SharedCtx, extract_active_ctx}; use crate::engine::workload::WorkloadComponent; use crate::plugin::HostPlugin; use crate::wit::{WitInterface, WitWorld}; @@ -25,7 +25,6 @@ mod bindings { use bindings::wasi::logging::logging::Level; use tokio::sync::RwLock; -use wasmtime::component::HasSelf; type ComponentMap = Arc>>; @@ -40,7 +39,7 @@ struct ComponentInfo { component_id: String, } -impl bindings::wasi::logging::logging::Host for Ctx { +impl<'a> bindings::wasi::logging::logging::Host for ActiveCtx<'a> { async fn log(&mut self, level: Level, context: String, message: String) -> anyhow::Result<()> { let Some(plugin) = self.get_plugin::(PLUGIN_LOGGING_ID) else { bail!("TracingLogging plugin not found in context"); @@ -148,9 +147,9 @@ impl HostPlugin for TracingLogging { } // Add `wasi:logging/logging` to the workload's linker - bindings::wasi::logging::logging::add_to_linker::<_, HasSelf>( + bindings::wasi::logging::logging::add_to_linker::<_, SharedCtx>( component.linker(), - |ctx| ctx, + extract_active_ctx, )?; self.components.write().await.insert( diff --git a/crates/wash-runtime/src/washlet/plugins/wasmcloud_messaging.rs b/crates/wash-runtime/src/washlet/plugins/wasmcloud_messaging.rs index 4e6eefa5..b40de42e 100644 --- a/crates/wash-runtime/src/washlet/plugins/wasmcloud_messaging.rs +++ b/crates/wash-runtime/src/washlet/plugins/wasmcloud_messaging.rs @@ -1,7 +1,7 @@ use std::collections::HashSet; use std::sync::Arc; -use crate::engine::ctx::Ctx; +use crate::engine::ctx::{ActiveCtx, SharedCtx, extract_active_ctx}; use crate::engine::workload::{ResolvedWorkload, WorkloadComponent}; use crate::plugin::HostPlugin; use crate::wit::{WitInterface, WitWorld}; @@ -23,7 +23,6 @@ mod bindings { use bindings::wasmcloud::messaging::consumer::Host; use bindings::wasmcloud::messaging::types; -use wasmtime::component::HasSelf; use crate::washlet::plugins::WorkloadTracker; @@ -47,7 +46,7 @@ impl WasmcloudMessaging { } } -impl Host for Ctx { +impl<'a> Host for ActiveCtx<'a> { #[instrument(level = "debug", skip_all, fields(subject = %subject, timeout_ms))] async fn request( &mut self, @@ -107,7 +106,7 @@ impl Host for Ctx { } } -impl types::Host for Ctx {} +impl<'a> types::Host for ActiveCtx<'a> {} #[async_trait::async_trait] impl HostPlugin for WasmcloudMessaging { @@ -137,13 +136,13 @@ impl HostPlugin for WasmcloudMessaging { return Ok(()); }; - bindings::wasmcloud::messaging::types::add_to_linker::<_, HasSelf>( + bindings::wasmcloud::messaging::types::add_to_linker::<_, SharedCtx>( component_handle.linker(), - |ctx| ctx, + extract_active_ctx, )?; - bindings::wasmcloud::messaging::consumer::add_to_linker::<_, HasSelf>( + bindings::wasmcloud::messaging::consumer::add_to_linker::<_, SharedCtx>( component_handle.linker(), - |ctx| ctx, + extract_active_ctx, )?; if interface.interfaces.iter().any(|i| i == "handler") { diff --git a/crates/wash/src/plugin/mod.rs b/crates/wash/src/plugin/mod.rs index 5fddc8ba..8819dfc1 100644 --- a/crates/wash/src/plugin/mod.rs +++ b/crates/wash/src/plugin/mod.rs @@ -23,7 +23,7 @@ use tokio::sync::RwLock; use tracing::{debug, error, info, instrument}; use wash_runtime::{ engine::{ - ctx::Ctx, + ctx::{SharedCtx, extract_active_ctx}, workload::{ResolvedWorkload, WorkloadComponent}, }, host::HostApi, @@ -35,7 +35,6 @@ use wash_runtime::{ }, wit::{WitInterface, WitWorld}, }; -use wasmtime::component::HasSelf; pub mod bindings; pub mod runner; @@ -278,9 +277,9 @@ impl HostPlugin for PluginManager { ); // Add the types interface (provides runner, context, etc. to components that import wasmcloud:wash/types) - bindings::wasmcloud::wash::types::add_to_linker::<_, HasSelf>( + bindings::wasmcloud::wash::types::add_to_linker::<_, SharedCtx>( component.linker(), - |ctx| ctx, + extract_active_ctx, )?; Ok(()) diff --git a/crates/wash/src/plugin/runner.rs b/crates/wash/src/plugin/runner.rs index 3a13da22..e01532aa 100644 --- a/crates/wash/src/plugin/runner.rs +++ b/crates/wash/src/plugin/runner.rs @@ -6,7 +6,7 @@ use dialoguer::{Confirm, theme::ColorfulTheme}; use std::{collections::HashMap, env, sync::Arc}; use tokio::{process::Command, sync::RwLock}; use tracing::{debug, warn}; -use wash_runtime::engine::ctx::Ctx; +use wash_runtime::engine::ctx::ActiveCtx; use wasmtime::component::Resource; use crate::plugin::{PLUGIN_MANAGER_ID, PluginManager, bindings::wasmcloud::wash::types::Metadata}; @@ -51,9 +51,9 @@ impl Default for ProjectConfig { pub type Context = Arc>>; pub type PluginConfig = HashMap; -impl crate::plugin::bindings::wasmcloud::wash::types::Host for Ctx {} +impl<'a> crate::plugin::bindings::wasmcloud::wash::types::Host for ActiveCtx<'a> {} /// The Context resource is a passthrough to the same map we use for runtime configuration -impl crate::plugin::bindings::wasmcloud::wash::types::HostContext for Ctx { +impl<'a> crate::plugin::bindings::wasmcloud::wash::types::HostContext for ActiveCtx<'a> { async fn get(&mut self, ctx: Resource, key: String) -> Option { let context = match self.table.get(&ctx) { Ok(context) => context, @@ -109,7 +109,7 @@ impl crate::plugin::bindings::wasmcloud::wash::types::HostContext for Ctx { } } -impl crate::plugin::bindings::wasmcloud::wash::types::HostProjectConfig for Ctx { +impl<'a> crate::plugin::bindings::wasmcloud::wash::types::HostProjectConfig for ActiveCtx<'a> { async fn version(&mut self, ctx: Resource) -> String { let c = self.table.get(&ctx).unwrap(); c.version.clone() @@ -123,7 +123,7 @@ impl crate::plugin::bindings::wasmcloud::wash::types::HostProjectConfig for Ctx } } -impl crate::plugin::bindings::wasmcloud::wash::types::HostRunner for Ctx { +impl<'a> crate::plugin::bindings::wasmcloud::wash::types::HostRunner for ActiveCtx<'a> { async fn context(&mut self, runner: Resource) -> Result, String> { let runner = self.table.get(&runner).map_err(|e| e.to_string())?; self.table @@ -239,7 +239,7 @@ impl crate::plugin::bindings::wasmcloud::wash::types::HostRunner for Ctx { } } -impl crate::plugin::bindings::wasmcloud::wash::types::HostPluginConfig for Ctx { +impl<'a> crate::plugin::bindings::wasmcloud::wash::types::HostPluginConfig for ActiveCtx<'a> { async fn get(&mut self, ctx: Resource, key: String) -> Option { let plugin_config = match self.table.get(&ctx) { Ok(plugin_config) => plugin_config, diff --git a/examples/inter-component-call-callee/.wash/config.json b/examples/inter-component-call-callee/.wash/config.json new file mode 100644 index 00000000..a52179fc --- /dev/null +++ b/examples/inter-component-call-callee/.wash/config.json @@ -0,0 +1,11 @@ +{ + "build": { + "rust": { + "target": "wasm32-wasip2", + "cargo_flags": [], + "release": false, + "features": [], + "no_default_features": false + } + } +} \ No newline at end of file diff --git a/examples/inter-component-call-callee/.wash/config.yaml b/examples/inter-component-call-callee/.wash/config.yaml new file mode 100644 index 00000000..1aea808a --- /dev/null +++ b/examples/inter-component-call-callee/.wash/config.yaml @@ -0,0 +1,4 @@ +build: + rust: + target: wasm32-wasip2 + release: true diff --git a/examples/inter-component-call-callee/Cargo.lock b/examples/inter-component-call-callee/Cargo.lock new file mode 100644 index 00000000..3e69b4f1 --- /dev/null +++ b/examples/inter-component-call-callee/Cargo.lock @@ -0,0 +1,440 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indexmap" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", + "serde", + "serde_core", +] + +[[package]] +name = "inter-component-call-receiver" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "bitflags", + "futures", + "once_cell", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/examples/inter-component-call-callee/Cargo.toml b/examples/inter-component-call-callee/Cargo.toml new file mode 100644 index 00000000..a0f9f917 --- /dev/null +++ b/examples/inter-component-call-callee/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "inter-component-call-callee" +version = "0.1.0" +edition = "2024" + +[workspace] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = "0.46.0" diff --git a/examples/inter-component-call-callee/src/lib.rs b/examples/inter-component-call-callee/src/lib.rs new file mode 100644 index 00000000..43bb5dbe --- /dev/null +++ b/examples/inter-component-call-callee/src/lib.rs @@ -0,0 +1,17 @@ +use crate::wasi::logging::logging; + +wit_bindgen::generate!({ + world: "component", + generate_all +}); + +struct Component; + +impl exports::wasmcloud::example::receiver::Guest for Component { + fn invoke() -> Result<(), String> { + logging::log(logging::Level::Debug, "receiver", "invoke"); + Ok(()) + } +} + +export!(Component); diff --git a/examples/inter-component-call-callee/wit/deps/wasi-logging-0.1.0-draft/package.wit b/examples/inter-component-call-callee/wit/deps/wasi-logging-0.1.0-draft/package.wit new file mode 100644 index 00000000..164cb5b9 --- /dev/null +++ b/examples/inter-component-call-callee/wit/deps/wasi-logging-0.1.0-draft/package.wit @@ -0,0 +1,36 @@ +package wasi:logging@0.1.0-draft; + +/// WASI Logging is a logging API intended to let users emit log messages with +/// simple priority levels and context values. +interface logging { + /// A log level, describing a kind of message. + enum level { + /// Describes messages about the values of variables and the flow of + /// control within a program. + trace, + /// Describes messages likely to be of interest to someone debugging a + /// program. + debug, + /// Describes messages likely to be of interest to someone monitoring a + /// program. + info, + /// Describes messages indicating hazardous situations. + warn, + /// Describes messages indicating serious errors. + error, + /// Describes messages indicating fatal errors. + critical, + } + + /// Emit a log message. + /// + /// A log message has a `level` describing what kind of message is being + /// sent, a context, which is an uninterpreted string meant to help + /// consumers group similar messages, and a string containing the message + /// text. + log: func(level: level, context: string, message: string); +} + +world imports { + import logging; +} diff --git a/examples/inter-component-call-callee/wit/world.wit b/examples/inter-component-call-callee/wit/world.wit new file mode 100644 index 00000000..7e3ffbc4 --- /dev/null +++ b/examples/inter-component-call-callee/wit/world.wit @@ -0,0 +1,10 @@ +package wasmcloud:example@0.0.1; + +interface receiver { + invoke: func() -> result<_, string>; +} + +world component { + import wasi:logging/logging@0.1.0-draft; + export receiver; +} diff --git a/examples/inter-component-call-callee/wkg.lock b/examples/inter-component-call-callee/wkg.lock new file mode 100644 index 00000000..5671fb9c --- /dev/null +++ b/examples/inter-component-call-callee/wkg.lock @@ -0,0 +1,12 @@ +# This file is automatically generated. +# It is not intended for manual editing. +version = 1 + +[[packages]] +name = "wasi:logging" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.1.0-draft" +version = "0.1.0-draft" +digest = "sha256:09621a45b12b0a9cddc798517f778aac0e5ae4bd234077b3d70758d6cf625580" diff --git a/examples/inter-component-call-caller/.gitignore b/examples/inter-component-call-caller/.gitignore new file mode 100644 index 00000000..9f17e8e8 --- /dev/null +++ b/examples/inter-component-call-caller/.gitignore @@ -0,0 +1,11 @@ +# Rust build artifacts +target/ + +# wash config +.wash + +# WIT dependencies +wit/deps + +# Trash +.DS_Store \ No newline at end of file diff --git a/examples/inter-component-call-caller/Cargo.lock b/examples/inter-component-call-caller/Cargo.lock new file mode 100644 index 00000000..126a53ea --- /dev/null +++ b/examples/inter-component-call-caller/Cargo.lock @@ -0,0 +1,440 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inter-component-call-sender" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "bitflags", + "futures", + "once_cell", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/examples/inter-component-call-caller/Cargo.toml b/examples/inter-component-call-caller/Cargo.toml new file mode 100644 index 00000000..1e0e3c51 --- /dev/null +++ b/examples/inter-component-call-caller/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "inter-component-call-sender" +edition = "2024" +version = "0.1.0" + +[workspace] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = "0.46.0" diff --git a/examples/inter-component-call-caller/src/lib.rs b/examples/inter-component-call-caller/src/lib.rs new file mode 100644 index 00000000..63a68308 --- /dev/null +++ b/examples/inter-component-call-caller/src/lib.rs @@ -0,0 +1,51 @@ +mod bindings { + wit_bindgen::generate!({ + world: "component", + generate_all + }); +} + +use bindings::{ + exports::wasi::http::incoming_handler::Guest, + wasi::http::types::{ + Fields, IncomingRequest, OutgoingBody, OutgoingResponse, ResponseOutparam, + }, +}; + +use crate::bindings::wasmcloud::example::receiver; + +struct Component; + +impl Guest for Component { + fn handle(_request: IncomingRequest, response_out: ResponseOutparam) { + match receiver::invoke() { + Ok(_) => { + let response = OutgoingResponse::new(Fields::new()); + response.set_status_code(200).unwrap(); + let body = response.body().unwrap(); + ResponseOutparam::set(response_out, Ok(response)); + + let stream = body.write().unwrap(); + stream.blocking_write_and_flush("".as_bytes()).unwrap(); + drop(stream); + OutgoingBody::finish(body, None).unwrap(); + } + Err(e) => { + let response = OutgoingResponse::new(Fields::new()); + response.set_status_code(500).unwrap(); + let body = response.body().unwrap(); + ResponseOutparam::set(response_out, Ok(response)); + + let error_msg = format!("Internal server error: {e}"); + let stream = body.write().unwrap(); + stream + .blocking_write_and_flush(error_msg.as_bytes()) + .unwrap(); + drop(stream); + OutgoingBody::finish(body, None).unwrap(); + } + } + } +} + +bindings::export!(Component with_types_in bindings); diff --git a/examples/inter-component-call-caller/wit/world.wit b/examples/inter-component-call-caller/wit/world.wit new file mode 100644 index 00000000..f6e0e0f9 --- /dev/null +++ b/examples/inter-component-call-caller/wit/world.wit @@ -0,0 +1,10 @@ +package wasmcloud:example@0.0.1; + +interface receiver { + invoke: func() -> result<_, string>; +} + +world component { + import receiver; + export wasi:http/incoming-handler@0.2.2; +} diff --git a/examples/inter-component-call-caller/wkg.lock b/examples/inter-component-call-caller/wkg.lock new file mode 100644 index 00000000..32104997 --- /dev/null +++ b/examples/inter-component-call-caller/wkg.lock @@ -0,0 +1,12 @@ +# This file is automatically generated. +# It is not intended for manual editing. +version = 1 + +[[packages]] +name = "wasi:http" +registry = "wasi.dev" + +[[packages.versions]] +requirement = "=0.2.2" +version = "0.2.2" +digest = "sha256:a1f129cdf1fde55ec2d4ae8d998c39a7e5cf7544a8bd84a831054ac0d2ac64dd" diff --git a/tests/fixtures/inter_component_call_callee.wasm b/tests/fixtures/inter_component_call_callee.wasm new file mode 100644 index 00000000..b038b720 Binary files /dev/null and b/tests/fixtures/inter_component_call_callee.wasm differ diff --git a/tests/fixtures/inter_component_call_caller.wasm b/tests/fixtures/inter_component_call_caller.wasm new file mode 100644 index 00000000..e2edc5b9 Binary files /dev/null and b/tests/fixtures/inter_component_call_caller.wasm differ diff --git a/tests/integration_inter_component_call.rs b/tests/integration_inter_component_call.rs new file mode 100644 index 00000000..4ccbc0e6 --- /dev/null +++ b/tests/integration_inter_component_call.rs @@ -0,0 +1,284 @@ +//! Integration test for http-counter component with blobstore-filesystem plugin +//! +//! This test demonstrates component-to-component linking by: +//! 1. Running the blobstore-filesystem plugin as a component that exports wasi:blobstore +//! 2. Running the http-counter component that imports wasi:blobstore +//! 3. Verifying that the http-counter can use the blobstore-filesystem implementation +//! 4. Testing the component resolution system that links them together + +use anyhow::{Context, Result}; +use std::{ + collections::{HashMap, HashSet}, + net::SocketAddr, + sync::Arc, + time::Duration, +}; +use tokio::{sync::Mutex, time::timeout}; + +mod common; +use common::find_available_port; + +use wash_runtime::{ + engine::{ + Engine, + ctx::{ActiveCtx, SharedCtx, extract_active_ctx}, + workload::{ResolvedWorkload, WorkloadComponent}, + }, + host::{ + HostApi, HostBuilder, + http::{DevRouter, HttpServer}, + }, + plugin::{HostPlugin, wasi_config::WasiConfig, wasi_keyvalue::WasiKeyvalue}, + types::{Component, LocalResources, Workload, WorkloadStartRequest}, + wit::{WitInterface, WitWorld}, +}; + +use wash::plugin::PluginManager; + +use crate::bindings::wasi::logging::logging::Level; + +mod bindings { + wasmtime::component::bindgen!({ + imports: { default: async | trappable }, + inline: " + package wasmcloud:runtime@0.1.0; + + world logging { + import wasi:logging/logging@0.1.0-draft; + } + " + }); +} + +const CALLER_WASM: &[u8] = include_bytes!("fixtures/inter_component_call_caller.wasm"); +const CALLEE_WASM: &[u8] = include_bytes!("fixtures/inter_component_call_callee.wasm"); + +#[derive(Clone)] +pub struct PerComponentInfo { + workload_id: String, +} + +#[derive(Default)] +pub struct CustomLogging { + tracker: Mutex>, +} + +impl<'a> bindings::wasi::logging::logging::Host for ActiveCtx<'a> { + async fn log(&mut self, level: Level, context: String, message: String) -> anyhow::Result<()> { + let plugin = self + .get_plugin::("logging") + .ok_or_else(|| anyhow::anyhow!("failed to get plugin"))?; + + let per_component_info = plugin + .tracker + .lock() + .await + .get(&*self.component_id) + .cloned(); + + if !per_component_info.is_some_and(|info| info.workload_id == &*self.workload_id) { + return Err(anyhow::anyhow!("workload ID mismatch")); + } + + match level { + Level::Critical => tracing::error!(id = &self.id, context, "{message}"), + Level::Error => tracing::error!(id = &self.id, context, "{message}"), + Level::Warn => tracing::warn!(id = &self.id, context, "{message}"), + Level::Info => tracing::info!(id = &self.id, context, "{message}"), + Level::Debug => tracing::debug!(id = &self.id, context, "{message}"), + Level::Trace => tracing::trace!(id = &self.id, context, "{message}"), + } + Ok(()) + } +} + +#[async_trait::async_trait] +impl HostPlugin for CustomLogging { + fn id(&self) -> &'static str { + "logging" + } + + fn world(&self) -> WitWorld { + WitWorld { + imports: HashSet::from([WitInterface::from("wasi:logging/logging")]), + ..Default::default() + } + } + + async fn on_component_bind( + &self, + workload_handle: &mut WorkloadComponent, + interfaces: std::collections::HashSet, + ) -> anyhow::Result<()> { + // Ensure exactly one interface: "wasi:logging/logging" + let mut iter = interfaces.iter(); + let Some(interface) = iter.next() else { + anyhow::bail!("No interfaces provided; expected wasi:logging/logging"); + }; + if iter.next().is_some() + || interface.namespace != "wasi" + || interface.package != "logging" + || !interface.interfaces.contains("logging") + { + anyhow::bail!( + "Expected exactly one interface: wasi:logging/logging, got: {:?}", + interfaces + ); + } + + // Add `wasi:logging/logging` to the workload's linker + bindings::wasi::logging::logging::add_to_linker::<_, SharedCtx>( + workload_handle.linker(), + extract_active_ctx, + )?; + + Ok(()) + } + + async fn on_workload_resolved( + &self, + workload: &ResolvedWorkload, + component_id: &str, + ) -> anyhow::Result<()> { + self.tracker.lock().await.insert( + component_id.to_string(), + PerComponentInfo { + workload_id: workload.id().to_string(), + }, + ); + Ok(()) + } +} + +#[tokio::test] +async fn test_inter_component_call() -> Result<()> { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); + + // Create engine + let engine = Engine::builder().build()?; + + // Create HTTP server plugin on a dynamically allocated port + let port = find_available_port().await?; + let addr: SocketAddr = format!("127.0.0.1:{port}").parse().unwrap(); + let http_plugin = HttpServer::new(DevRouter::default(), addr); + + // Create keyvalue plugin for counter persistence (still using built-in) + let keyvalue_plugin = WasiKeyvalue::new(); + + // Create logging plugin + let logging_plugin = CustomLogging::default(); + + // Create config plugin + let config_plugin = WasiConfig::default(); + + // Create plugin manager to provide wasmcloud:wash interfaces + let plugin_manager = PluginManager::default(); + + // Build host WITHOUT the built-in blobstore plugin + // We'll use the blobstore-filesystem component instead + let host = HostBuilder::new() + .with_engine(engine.clone()) + .with_http_handler(Arc::new(http_plugin)) + .with_plugin(Arc::new(keyvalue_plugin))? + .with_plugin(Arc::new(logging_plugin))? + .with_plugin(Arc::new(config_plugin))? + .with_plugin(Arc::new(plugin_manager))? + .build()?; + + // Start the host (which starts all plugins) + let host = host.start().await.context("Failed to start host")?; + println!("Host started, HTTP server listening on {addr}"); + + let req = WorkloadStartRequest { + workload_id: uuid::Uuid::new_v4().to_string(), + workload: Workload { + namespace: "test".to_string(), + name: "caller".to_string(), + annotations: HashMap::new(), + service: None, + components: vec![ + Component { + name: "caller".to_string(), + bytes: bytes::Bytes::from_static(CALLER_WASM), + local_resources: LocalResources { + memory_limit_mb: 128, + cpu_limit: 1, + config: HashMap::new(), + environment: HashMap::new(), + volume_mounts: vec![], + allowed_hosts: vec![], + }, + pool_size: 1, + max_invocations: 100, + }, + Component { + name: "callee".to_string(), + bytes: bytes::Bytes::from_static(CALLEE_WASM), + local_resources: LocalResources { + memory_limit_mb: 256, + cpu_limit: 2, + config: HashMap::new(), + environment: HashMap::new(), + volume_mounts: vec![], + allowed_hosts: vec!["example.com".to_string()], + }, + pool_size: 2, + max_invocations: 100, + }, + ], + host_interfaces: vec![ + WitInterface { + namespace: "wasi".to_string(), + package: "http".to_string(), + interfaces: ["incoming-handler".to_string()].into_iter().collect(), + version: None, + config: { + let mut config = HashMap::new(); + config.insert("host".to_string(), "test".to_string()); + config + }, + }, + WitInterface { + namespace: "wasi".to_string(), + package: "logging".to_string(), + interfaces: ["logging".to_string()].into_iter().collect(), + version: Some(semver::Version::parse("0.1.0-draft").unwrap()), + config: HashMap::new(), + }, + ], + volumes: vec![], + }, + }; + + let _ = host + .workload_start(req) + .await + .context("Failed to start workload with component linking")?; + + let client = reqwest::Client::new(); + + println!("Testing inter-component call"); + let response = timeout( + Duration::from_secs(10), + client + .get(format!("http://{addr}/")) + .header("HOST", "test") + .send(), + ) + .await + .context("First request timed out")? + .context("Failed to make first request")?; + + let status = response.status(); + println!("First Response Status: {}", status); + + assert!( + status.is_success(), + "First request failed with status {}", + status, + ); + + Ok(()) +}