Skip to content

Commit cf13898

Browse files
authored
Include core instance sizes in component_instance_size limit (#12772)
* Include core instance sizes in component_instance_size limit There exist several knobs for limiting the memory that might be consumed for metadata for components. For core module instances within a component, the two that previously existed to control metadata allocations have been: - A: max_core_instances_per_component - B: component_instance_size These allow for an embedder to set an upper bound on memory used by a component's instances to A * B. This value could be quite large for some systems and it would be nice to be able to set a cap on the total memory that might be used for metadata across all instances while still allowing for a greater number of instances with the potential for a subset of those instances to be relatively large. To allow for aggregate control over memory used within the runtime for componenets, the existing `max_component_instance_size` limit is extended to consider both the `VMComponentCtx` size as well as the aggregate size of all core instances in the component. * Fix err msg checks for component_instance_size_limit test * Miri ignore component_core_instances_aggregate_size
1 parent 133a0ef commit cf13898

3 files changed

Lines changed: 77 additions & 17 deletions

File tree

crates/wasmtime/src/config.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3650,7 +3650,8 @@ impl PoolingAllocationConfig {
36503650
}
36513651

36523652
/// The maximum size, in bytes, allocated for a component instance's
3653-
/// `VMComponentContext` metadata.
3653+
/// `VMComponentContext` metadata as well as the aggregate size of this
3654+
/// component's core instances `VMContext` metadata.
36543655
///
36553656
/// The [`wasmtime::component::Instance`][crate::component::Instance] type
36563657
/// has a static size but its internal `VMComponentContext` is dynamically
@@ -3667,10 +3668,17 @@ impl PoolingAllocationConfig {
36673668
/// module will fail at runtime with an error indicating how many bytes were
36683669
/// needed.
36693670
///
3671+
/// In addition to the memory in the runtime for the component itself,
3672+
/// components contain one or more core module instances. Each of these
3673+
/// require some memory in the runtime as described in
3674+
/// [`PoolingAllocationConfig::max_core_instance_size`]. The limit here
3675+
/// applies against the sum of all of these individual allocations.
3676+
///
36703677
/// The default value for this is 1MiB.
36713678
///
3672-
/// This provides an upper-bound on the total size of component
3673-
/// metadata-related allocations, along with
3679+
/// This provides an upper-bound on the total size of all component's
3680+
/// metadata-related allocations (for both the component and its embedded
3681+
/// core module instances), along with
36743682
/// [`PoolingAllocationConfig::total_component_instances`]. The upper bound is
36753683
///
36763684
/// ```text

crates/wasmtime/src/runtime/vm/instance/allocator/pooling.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ pub struct InstanceLimits {
100100
/// concurrently.
101101
pub total_component_instances: u32,
102102

103-
/// The maximum size of a component's `VMComponentContext`, not including
104-
/// any of its inner core modules' `VMContext` sizes.
103+
/// The maximum size of a component's `VMComponentContext`, including
104+
/// the aggregate size of all its inner core modules' `VMContext` sizes.
105105
pub component_instance_size: usize,
106106

107107
/// The maximum number of core module instances that may be allocated
@@ -474,18 +474,21 @@ impl PoolingInstanceAllocator {
474474
fn validate_component_instance_size(
475475
&self,
476476
offsets: &VMComponentOffsets<HostPtr>,
477+
core_instances_aggregate_size: usize,
477478
) -> Result<()> {
478-
if usize::try_from(offsets.size_of_vmctx()).unwrap() <= self.limits.component_instance_size
479-
{
479+
let vmcomponentctx_size = usize::try_from(offsets.size_of_vmctx()).unwrap();
480+
let total_instance_size = core_instances_aggregate_size.saturating_add(vmcomponentctx_size);
481+
if total_instance_size <= self.limits.component_instance_size {
480482
return Ok(());
481483
}
482484

483485
// TODO: Add context with detailed accounting of what makes up all the
484486
// `VMComponentContext`'s space like we do for module instances.
485487
bail!(
486-
"instance allocation for this component requires {} bytes of `VMComponentContext` \
487-
space which exceeds the configured maximum of {} bytes",
488-
offsets.size_of_vmctx(),
488+
"instance allocation for this component requires {total_instance_size} bytes of `VMComponentContext` \
489+
and aggregated core instance runtime space which exceeds the configured maximum of {} bytes. \
490+
`VMComponentContext` used {vmcomponentctx_size} bytes, `core module instances` used \
491+
{core_instances_aggregate_size} bytes.",
489492
self.limits.component_instance_size
490493
)
491494
}
@@ -559,12 +562,10 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
559562
offsets: &VMComponentOffsets<HostPtr>,
560563
get_module: &'a dyn Fn(StaticModuleIndex) -> &'a Module,
561564
) -> Result<()> {
562-
self.validate_component_instance_size(offsets)
563-
.context("component instance size does not fit in pooling allocator requirements")?;
564-
565565
let mut num_core_instances = 0;
566566
let mut num_memories = 0;
567567
let mut num_tables = 0;
568+
let mut core_instances_aggregate_size: usize = 0;
568569
for init in &component.initializers {
569570
use wasmtime_environ::component::GlobalInitializer::*;
570571
use wasmtime_environ::component::InstantiateModule;
@@ -577,10 +578,12 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
577578
InstantiateModule(InstantiateModule::Static(static_module_index, _), _) => {
578579
let module = get_module(*static_module_index);
579580
let offsets = VMOffsets::new(HostPtr, &module);
581+
let layout = Instance::alloc_layout(&offsets);
580582
self.validate_module(module, &offsets)?;
581583
num_core_instances += 1;
582584
num_memories += module.num_defined_memories();
583585
num_tables += module.num_defined_tables();
586+
core_instances_aggregate_size += layout.size();
584587
}
585588
LowerImport { .. }
586589
| ExtractMemory(_)
@@ -618,6 +621,9 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
618621
);
619622
}
620623

624+
self.validate_component_instance_size(offsets, core_instances_aggregate_size)
625+
.context("component instance size does not fit in pooling allocator requirements")?;
626+
621627
Ok(())
622628
}
623629

tests/all/pooling_allocator.rs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -858,10 +858,11 @@ fn component_instance_size_limit() -> Result<()> {
858858

859859
match wasmtime::component::Component::new(&engine, "(component)") {
860860
Ok(_) => panic!("should have hit limit"),
861-
Err(e) => e.assert_contains(
862-
"instance allocation for this component requires 64 bytes of \
863-
`VMComponentContext` space which exceeds the configured maximum of 1 bytes",
864-
),
861+
Err(e) => {
862+
e.assert_contains("instance allocation for this component requires 64 bytes");
863+
e.assert_contains("which exceeds the configured maximum of 1 bytes");
864+
e.assert_contains("`VMComponentContext` used 64 bytes");
865+
}
865866
}
866867

867868
Ok(())
@@ -1120,6 +1121,51 @@ fn component_tables_limit() -> Result<()> {
11201121
Ok(())
11211122
}
11221123

1124+
#[test]
1125+
#[cfg(feature = "component-model")]
1126+
#[cfg_attr(miri, ignore)]
1127+
fn component_core_instances_aggregate_size() -> Result<()> {
1128+
let mut pool = crate::small_pool_config();
1129+
pool.max_core_instances_per_component(100)
1130+
// x86_64 requires 23824 bytes; we exceed this by a fair bit as there will
1131+
// be differences by arch.
1132+
.max_component_instance_size(1024);
1133+
1134+
let mut config = Config::new();
1135+
config.wasm_component_model(true);
1136+
config.allocation_strategy(pool);
1137+
let engine = Engine::new(&config)?;
1138+
1139+
let core_instances = (1..100)
1140+
.map(|i| format!("(core instance $i{i} (instantiate $m))"))
1141+
.collect::<Vec<String>>()
1142+
.join("\n");
1143+
1144+
match wasmtime::component::Component::new(
1145+
&engine,
1146+
format!(
1147+
"
1148+
(component
1149+
(core module $m
1150+
(func (export \"f\") (result i32)
1151+
i32.const 42
1152+
)
1153+
)
1154+
{core_instances}
1155+
)
1156+
"
1157+
),
1158+
) {
1159+
Ok(_) => panic!("should have hit aggregate size limit"),
1160+
Err(e) => {
1161+
e.assert_contains("instance allocation for this component requires");
1162+
e.assert_contains("exceeds the configured maximum");
1163+
}
1164+
}
1165+
1166+
Ok(())
1167+
}
1168+
11231169
#[test]
11241170
#[cfg_attr(miri, ignore)]
11251171
fn total_memories_limit() -> Result<()> {

0 commit comments

Comments
 (0)