mirror of
https://github.com/openai/codex.git
synced 2026-09-10 20:26:47 +00:00
Allow discarded code mode tool responses to be garbage collected (#44619)
## Why Tool response delivery handles kept discarded results alive until the cell ended. ## What changed Add a local V8 handle scope in `resolve_tool_response` so delivery handles are released when response delivery finishes. Live promises continue to retain their results. ## Testing Add a regression test that verifies a live promise preserves its response through garbage collection and that dropping the promise makes the response collectible before the cell ends. GitOrigin-RevId: a74e78acae3ec9a465694d7503150aee123792ec
This commit is contained in:
@@ -76,6 +76,8 @@ pub(super) fn resolve_tool_response(
|
||||
}
|
||||
.ok_or_else(|| format!("unknown tool call `{id}`"))?;
|
||||
|
||||
// Release delivery handles before the cell ends; live promises retain their results.
|
||||
v8::scope!(let scope, scope);
|
||||
let tc = std::pin::pin!(v8::TryCatch::new(scope));
|
||||
let mut tc = tc.init();
|
||||
let resolver = v8::Local::new(&tc, &resolver);
|
||||
@@ -233,3 +235,7 @@ fn resolve_module<'s>(
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "module_loader_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
//! Checks that tool response delivery preserves live results without rooting discarded values.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc as std_mpsc;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use super::super::RuntimeState;
|
||||
use super::super::value::v8_value_to_json;
|
||||
use super::resolve_tool_response;
|
||||
use crate::v8_init::ensure_v8_initialized;
|
||||
|
||||
#[test]
|
||||
fn discarded_tool_response_is_collectible_before_cell_ends() {
|
||||
ensure_v8_initialized().expect("initialize V8");
|
||||
let isolate = &mut v8::Isolate::new(v8::CreateParams::default());
|
||||
v8::scope!(let scope, isolate);
|
||||
let context = v8::Context::new(scope, Default::default());
|
||||
let scope = &mut v8::ContextScope::new(scope, context);
|
||||
let (event_tx, _event_rx) = mpsc::unbounded_channel();
|
||||
let (runtime_command_tx, _runtime_command_rx) = std_mpsc::channel();
|
||||
scope.set_slot(RuntimeState {
|
||||
event_tx,
|
||||
pending_tool_calls: HashMap::new(),
|
||||
pending_timeouts: HashMap::new(),
|
||||
stored_values: HashMap::new(),
|
||||
stored_value_writes: HashMap::new(),
|
||||
enabled_tools: Vec::new(),
|
||||
next_tool_call_id: 1,
|
||||
next_timeout_id: 1,
|
||||
tool_call_id: "cell".to_string(),
|
||||
runtime_command_tx,
|
||||
exit_requested: false,
|
||||
});
|
||||
|
||||
let promise = {
|
||||
v8::scope!(let scope, scope);
|
||||
let resolver = v8::PromiseResolver::new(scope).expect("create tool promise");
|
||||
let promise = resolver.get_promise(scope);
|
||||
let resolver = v8::Global::new(scope, resolver);
|
||||
scope
|
||||
.get_slot_mut::<RuntimeState>()
|
||||
.expect("runtime state")
|
||||
.pending_tool_calls
|
||||
.insert("tool".to_string(), resolver);
|
||||
v8::Global::new(scope, promise)
|
||||
};
|
||||
let response = json!({"content": [{"type": "text", "text": "tool result"}]});
|
||||
resolve_tool_response(scope, "tool", Ok(response.clone())).expect("resolve tool promise");
|
||||
scope.perform_microtask_checkpoint();
|
||||
scope.low_memory_notification();
|
||||
|
||||
let weak_result = {
|
||||
v8::scope!(let scope, scope);
|
||||
let promise = v8::Local::new(scope, &promise);
|
||||
assert_eq!(promise.state(), v8::PromiseState::Fulfilled);
|
||||
let result = promise.result(scope);
|
||||
assert_eq!(v8_value_to_json(scope, result), Ok(Some(response)));
|
||||
v8::Weak::new(scope, result)
|
||||
};
|
||||
drop(promise);
|
||||
scope.low_memory_notification();
|
||||
|
||||
assert!(weak_result.is_empty(), "discarded response is still rooted");
|
||||
}
|
||||
Reference in New Issue
Block a user