scope nested component props without deep copies

This commit is contained in:
udo 2026-07-14 22:41:55 +00:00
parent 0bcf9a676e
commit 6b18587581
5 changed files with 98 additions and 13 deletions

View File

@ -401,6 +401,11 @@ header free-functions are `inline`. The wasm backend exposes only declarations
share one source, compile, artifact, and module-cache identity. The compiler share one source, compile, artifact, and module-cache identity. The compiler
independently enforces the same canonical identity, and persisted failures independently enforces the same canonical identity, and persisted failures
are reused only when their recorded source path matches the current request. are reused only when their recorded source path matches the current request.
`scripts/test_nested_component_props.sh` passes a large prop tree through an
outer component, invokes 300 nested components, and verifies both inner and
caller props are restored. Its warm-request ceiling guards the request-scope
invariant that entering a nested component swaps the active prop trees in
constant time instead of deep-copying the outer tree for every child.
- **Log timeliness**: the base process line-buffers stdout before forking - **Log timeliness**: the base process line-buffers stdout before forking
workers, the proactive compiler, and the WebSocket broker. This keeps each workers, the proactive compiler, and the WebSocket broker. This keeps each

View File

@ -68,6 +68,7 @@ if [[ "$action" == "run" ]]; then
done done
scripts/test_dependency_invalidation.sh scripts/test_dependency_invalidation.sh
scripts/test_cold_component_deadline.sh scripts/test_cold_component_deadline.sh
scripts/test_nested_component_props.sh
scripts/test_password_hashing.sh scripts/test_password_hashing.sh
scripts/test_log_timeliness.sh scripts/test_log_timeliness.sh
fi fi

View File

@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
test_name="nested-component-props-test-$$"
site_directory="${UCE_TEST_SITE_DIRECTORY:-site}"
if [[ -z "${UCE_TEST_SITE_DIRECTORY:-}" && -r /etc/uce/settings.cfg ]]; then
configured_site_directory=$(awk -F= '/^[[:space:]]*SITE_DIRECTORY[[:space:]]*=/ {gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' /etc/uce/settings.cfg)
if [[ -n "${configured_site_directory:-}" ]]; then
site_directory="$configured_site_directory"
fi
fi
source_dir="$site_directory/$test_name"
threshold_ms="${UCE_NESTED_COMPONENT_PROPS_MAX_MS:-1500}"
cleanup() {
rm -rf "$source_dir"
}
trap cleanup EXIT
mkdir -p "$source_dir"
printf '%s\n' \
'CLI(Request& context) {' \
' context.props["sentinel"] = "caller";' \
' String payload; for(u64 i = 0; i < 4096; ++i) payload += "x";' \
' DValue props; for(u64 i = 0; i < 300; ++i) { DValue item; item = payload; props["items"].push(item); }' \
' String output = component("outer", props, context);' \
' if(context.props["sentinel"].to_string() != "caller") { print("caller props not restored"); return; }' \
' print(output);' \
'}' >"$source_dir/parent.uce"
printf '%s\n' \
'COMPONENT(Request& context) {' \
' if(context.props["items"]._map.size() != 300) { print("outer props missing"); return; }' \
' for(u64 i = 0; i < 300; ++i) {' \
' DValue props; props["index"] = std::to_string(i); component("leaf", props, context);' \
' if(context.props["items"]._map.size() != 300) { print("outer props not restored"); return; }' \
' }' \
' print("nested-component-props-ok");' \
'}' >"$source_dir/outer.uce"
printf '%s\n' \
'COMPONENT(Request& context) {' \
' if(context.props["index"].to_string() == "") print("leaf props missing");' \
'}' >"$source_dir/leaf.uce"
warm_output=$(scripts/uce-cli "/$test_name/parent.uce")
if [[ "$warm_output" != "nested-component-props-ok" ]]; then
echo "nested component props warmup failed: $warm_output" >&2
exit 1
fi
start_ns=$(date +%s%N)
output=$(scripts/uce-cli "/$test_name/parent.uce")
elapsed_ms=$(( ($(date +%s%N) - start_ns) / 1000000 ))
if [[ "$output" != "nested-component-props-ok" ]]; then
echo "nested component props failed: $output" >&2
exit 1
fi
if (( elapsed_ms > threshold_ms )); then
echo "nested component props took ${elapsed_ms}ms (limit ${threshold_ms}ms)" >&2
exit 1
fi
echo "nested component props passed in ${elapsed_ms}ms"

View File

@ -12,11 +12,11 @@ struct DValue {
char type = 'S'; char type = 'S';
String _String; String _String;
f64 _float; f64 _float = 0;
s64 _array_index = 0; s64 _array_index = 0;
bool _bool; bool _bool = false;
bool _list_mode = false; bool _list_mode = false;
void* _ptr; void* _ptr = 0;
std::map<String, DValue> _map; std::map<String, DValue> _map;
// Read accessors are const and never create or modify nodes. The to_* // Read accessors are const and never create or modify nodes. The to_*

View File

@ -494,20 +494,32 @@ struct RequestPropsScope
Request* context = 0; Request* context = 0;
DValue previous_props; DValue previous_props;
RequestPropsScope(Request* context, const DValue& props) static void swap_value(DValue& left, DValue& right)
{
std::swap(left.type, right.type);
left._String.swap(right._String);
std::swap(left._float, right._float);
std::swap(left._array_index, right._array_index);
std::swap(left._bool, right._bool);
std::swap(left._list_mode, right._list_mode);
std::swap(left._ptr, right._ptr);
left._map.swap(right._map);
}
RequestPropsScope(Request* context, DValue& props)
{ {
this->context = context; this->context = context;
if(this->context) if(this->context)
{ {
previous_props = this->context->props; swap_value(previous_props, this->context->props);
this->context->props = props; swap_value(this->context->props, props);
} }
} }
~RequestPropsScope() ~RequestPropsScope()
{ {
if(context) if(context)
context->props = previous_props; swap_value(context->props, previous_props);
} }
}; };
@ -600,7 +612,8 @@ DValue* unit_call(String file_name, String function_name, DValue* call_param)
print("Error: unit_call() function '", function_name, "' not found"); print("Error: unit_call() function '", function_name, "' not found");
return(0); return(0);
} }
RequestPropsScope props_scope(context, (call_param ? *call_param : DValue())); DValue props = call_param ? *call_param : DValue();
RequestPropsScope props_scope(context, props);
if((handler == "render" || handler.rfind("render:", 0) == 0 || handler == "component" || handler.rfind("component:", 0) == 0) && resolved != "") if((handler == "render" || handler.rfind("render:", 0) == 0 || handler == "component" || handler.rfind("component:", 0) == 0) && resolved != "")
wasm_run_once(resolved, *context); wasm_run_once(resolved, *context);
String previous_unit = context->resources.current_unit_file; String previous_unit = context->resources.current_unit_file;
@ -634,7 +647,7 @@ DValue* unit_call(String file_name, String function_name, DValue* call_param)
return(0); return(0);
} }
void component_render(String name, DValue props, Request& request) static void component_render_with_props(String name, DValue& props, Request& request)
{ {
String file_name, render_name; String file_name, render_name;
component_parse_target(trim(name), file_name, render_name); component_parse_target(trim(name), file_name, render_name);
@ -658,14 +671,15 @@ void component_render(String name, DValue props, Request& request)
request.resources.current_unit_file = previous_unit; request.resources.current_unit_file = previous_unit;
} }
void component_render(String name) { DValue props; component_render(name, props, *context); } void component_render(String name, DValue props, Request& request) { component_render_with_props(name, props, request); }
void component_render(String name, Request& request) { DValue props; component_render(name, props, request); } void component_render(String name) { DValue props; component_render_with_props(name, props, *context); }
void component_render(String name, DValue props) { component_render(name, props, *context); } void component_render(String name, Request& request) { DValue props; component_render_with_props(name, props, request); }
void component_render(String name, DValue props) { component_render_with_props(name, props, *context); }
String component(String name, DValue props, Request& request) String component(String name, DValue props, Request& request)
{ {
ob_start(); ob_start();
component_render(name, props, request); component_render_with_props(name, props, request);
return(ob_get_close()); return(ob_get_close());
} }