From 22e2c7905b6a3fb1c8c0a65f1fb323b35942b177 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Fri, 6 Sep 2024 09:07:36 +1000 Subject: [PATCH 1/3] chore: remove `core.metrics()` --- core/01_core.js | 19 ---------- core/lib.rs | 2 -- core/ops_metrics.rs | 76 --------------------------------------- core/runtime/tests/ops.rs | 55 ---------------------------- ops/op2/valid_args.md | 46 ++++++++++++------------ 5 files changed, 23 insertions(+), 175 deletions(-) diff --git a/core/01_core.js b/core/01_core.js index ea79449e1..6e1e815d8 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -260,24 +260,6 @@ return ObjectFromEntries(op_resources()); } - function metrics() { - // TODO(mmastrac): we should replace this with a newer API - return { - opsDispatched: 0, - opsDispatchedSync: 0, - opsDispatchedAsync: 0, - opsDispatchedAsyncUnref: 0, - opsCompleted: 0, - opsCompletedSync: 0, - opsCompletedAsync: 0, - opsCompletedAsyncUnref: 0, - bytesSentControl: 0, - bytesSentData: 0, - bytesReceived: 0, - ops: {}, - }; - } - let reportExceptionCallback = (error) => { op_dispatch_exception(error, false); }; @@ -650,7 +632,6 @@ const core = ObjectAssign(globalThis.Deno.core, { internalRidSymbol: Symbol("Deno.internal.rid"), resources, - metrics, eventLoopTick, BadResource, BadResourcePrototype, diff --git a/core/lib.rs b/core/lib.rs index dbd510d00..47b5d4876 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -142,8 +142,6 @@ pub use crate::ops_metrics::OpMetricsEvent; pub use crate::ops_metrics::OpMetricsFactoryFn; pub use crate::ops_metrics::OpMetricsFn; pub use crate::ops_metrics::OpMetricsSource; -pub use crate::ops_metrics::OpMetricsSummary; -pub use crate::ops_metrics::OpMetricsSummaryTracker; pub use crate::path::strip_unc_prefix; pub use crate::runtime::stats; pub use crate::runtime::CompiledWasmModuleStore; diff --git a/core/ops_metrics.rs b/core/ops_metrics.rs index fbcd43e23..ad2d24ef8 100644 --- a/core/ops_metrics.rs +++ b/core/ops_metrics.rs @@ -4,9 +4,6 @@ use crate::ops::OpCtx; use crate::serde::Serialize; use crate::OpDecl; use crate::OpId; -use std::cell::Ref; -use std::cell::RefCell; -use std::cell::RefMut; use std::rc::Rc; /// The type of op metrics event. @@ -116,76 +113,3 @@ impl OpMetricsSummary { self.ops_dispatched_async > self.ops_completed_async } } - -#[derive(Default, Debug)] -pub struct OpMetricsSummaryTracker { - ops: RefCell>, -} - -impl OpMetricsSummaryTracker { - pub fn per_op(&self) -> Ref<'_, Vec> { - self.ops.borrow() - } - - pub fn aggregate(&self) -> OpMetricsSummary { - let mut sum = OpMetricsSummary::default(); - - for metrics in self.ops.borrow().iter() { - sum.ops_dispatched_sync += metrics.ops_dispatched_sync; - sum.ops_dispatched_fast += metrics.ops_dispatched_fast; - sum.ops_dispatched_async += metrics.ops_dispatched_async; - sum.ops_completed_async += metrics.ops_completed_async; - } - - sum - } - - #[inline] - fn metrics_mut(&self, id: OpId) -> RefMut { - RefMut::map(self.ops.borrow_mut(), |ops| &mut ops[id as usize]) - } - - /// Returns a [`OpMetricsFn`] for this tracker. - fn op_metrics_fn(self: Rc) -> OpMetricsFn { - Rc::new(move |ctx, event, source| match event { - OpMetricsEvent::Dispatched => { - let mut m = self.metrics_mut(ctx.id); - if source == OpMetricsSource::Fast { - m.ops_dispatched_fast += 1; - } - if ctx.decl.is_async { - m.ops_dispatched_async += 1; - } else { - m.ops_dispatched_sync += 1; - } - } - OpMetricsEvent::Completed - | OpMetricsEvent::Error - | OpMetricsEvent::CompletedAsync - | OpMetricsEvent::ErrorAsync => { - if ctx.decl.is_async { - self.metrics_mut(ctx.id).ops_completed_async += 1; - } - } - }) - } - - /// Retrieves the metrics factory function for this tracker. - pub fn op_metrics_factory_fn( - self: Rc, - op_enabled: impl Fn(&OpDecl) -> bool + 'static, - ) -> OpMetricsFactoryFn { - Box::new(move |_, total, op| { - let mut ops = self.ops.borrow_mut(); - if ops.capacity() == 0 { - ops.reserve_exact(total); - } - ops.push(OpMetricsSummary::default()); - if op_enabled(op) { - Some(self.clone().op_metrics_fn()) - } else { - None - } - }) - } -} diff --git a/core/runtime/tests/ops.rs b/core/runtime/tests/ops.rs index f0c9be77e..9f5916b92 100644 --- a/core/runtime/tests/ops.rs +++ b/core/runtime/tests/ops.rs @@ -685,58 +685,3 @@ op_async_arg_error Dispatched op_async_arg_error Error"# ); } - -#[tokio::test] -pub async fn test_op_metrics_summary_tracker() { - let tracker = Rc::new(OpMetricsSummaryTracker::default()); - // We want to limit the tracker to just the ops we care about - let op_enabled = |op: &OpDecl| { - op.name.starts_with("op_async") || op.name.starts_with("op_sync") - }; - let mut runtime = JsRuntime::new(RuntimeOptions { - extensions: vec![test_ext::init_ops()], - op_metrics_factory_fn: Some( - tracker.clone().op_metrics_factory_fn(op_enabled), - ), - ..Default::default() - }); - - let promise = runtime - .execute_script( - "filename.js", - r#" - const { op_sync, op_sync_error, op_async, op_async_error, op_async_yield, op_async_yield_error, op_async_deferred, op_async_lazy, op_async_impl_future_error, op_sync_arg_error, op_async_arg_error } = Deno.core.ops; - async function go() { - op_sync(); - try { op_sync_error(); } catch {} - await op_async(); - try { await op_async_error() } catch {} - await op_async_yield(); - try { await op_async_yield_error() } catch {} - await op_async_deferred(); - await op_async_lazy(); - try { await op_async_impl_future_error() } catch {} - try { op_sync_arg_error() } catch {} - try { await op_async_arg_error() } catch {} - } - - go() - "#, - ) - .unwrap(); - #[allow(deprecated)] - runtime - .resolve_value(promise) - .await - .expect("Failed to await promise"); - drop(runtime); - assert_eq!( - tracker.aggregate(), - OpMetricsSummary { - ops_completed_async: 8, - ops_dispatched_async: 8, - ops_dispatched_sync: 3, - ops_dispatched_fast: 0, - } - ); -} diff --git a/ops/op2/valid_args.md b/ops/op2/valid_args.md index 7933088cc..e97004c89 100644 --- a/ops/op2/valid_args.md +++ b/ops/op2/valid_args.md @@ -22,38 +22,38 @@ | X | &v8::**V8** | X | **V8** | | | X | v8::Local | X | any | | | X | v8::Local | X | **V8** | | -| X | #[global] v8::Global | | any | ⚠️ Slower than `v8::Local`. | -| X | #[global] v8::Global | | **V8** | ⚠️ Slower than `v8::Local`. | -| X | #[serde] SerdeType | | any | ⚠️ May be slow. | -| X | #[serde] (Tuple, Tuple) | | any | ⚠️ May be slow. | -| | #[anybuffer] &mut [u8] | X | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | -| | #[anybuffer] &[u8] | X | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | -| | #[anybuffer] *mut u8 | X | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | -| | #[anybuffer] *const u8 | X | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | -| X | #[arraybuffer] &mut [u8] | X | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | -| X | #[arraybuffer] &[u8] | X | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | -| X | #[arraybuffer] *mut u8 | X | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | -| X | #[arraybuffer] *const u8 | X | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | +| X | #[global] v8::Global | | any | ⚠️ Slower than `v8::Local`. | +| X | #[global] v8::Global | | **V8** | ⚠️ Slower than `v8::Local`. | +| X | #[serde] SerdeType | | any | ⚠️ May be slow. | +| X | #[serde] (Tuple, Tuple) | | any | ⚠️ May be slow. | +| | #[anybuffer] &mut [u8] | X | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | +| | #[anybuffer] &[u8] | X | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | +| | #[anybuffer] *mut u8 | X | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | +| | #[anybuffer] *const u8 | X | ArrayBuffer, ArrayBufferView (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | +| X | #[arraybuffer] &mut [u8] | X | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | +| X | #[arraybuffer] &[u8] | X | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | +| X | #[arraybuffer] *mut u8 | X | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | +| X | #[arraybuffer] *const u8 | X | ArrayBuffer (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | | X | #[arraybuffer(copy)] Vec | X | ArrayBuffer (resizable=true,false) | Safe, but forces a copy. | | X | #[arraybuffer(copy)] Box<[u8]> | X | ArrayBuffer (resizable=true,false) | Safe, but forces a copy. | | X | #[arraybuffer(copy)] bytes::Bytes | X | ArrayBuffer (resizable=true,false) | Safe, but forces a copy. | -| | #[buffer] &mut [u8] | X | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | -| | #[buffer] &[u8] | X | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | -| | #[buffer] *mut u8 | X | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | -| | #[buffer] *const u8 | X | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | +| | #[buffer] &mut [u8] | X | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | +| | #[buffer] &[u8] | X | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | +| | #[buffer] *mut u8 | X | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | +| | #[buffer] *const u8 | X | UInt8Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. Because of how V8 treats empty arrays in fastcalls, they will always be passed as null. | | X | #[buffer(copy)] Vec | X | UInt8Array (resizable=true,false) | Safe, but forces a copy. | | X | #[buffer(copy)] Box<[u8]> | X | UInt8Array (resizable=true,false) | Safe, but forces a copy. | | X | #[buffer(copy)] bytes::Bytes | X | UInt8Array (resizable=true,false) | Safe, but forces a copy. | -| X | #[buffer] &mut [u32] | X | UInt32Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | -| X | #[buffer] &[u32] | X | UInt32Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | +| X | #[buffer] &mut [u32] | X | UInt32Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | +| X | #[buffer] &[u32] | X | UInt32Array (resizable=true,false) | ⚠️ JS may modify the contents of the slice if V8 is called re-entrantly. | | X | #[buffer(copy)] Vec | X | UInt32Array (resizable=true,false) | Safe, but forces a copy. | | X | #[buffer(copy)] Box<[u32]> | X | UInt32Array (resizable=true,false) | Safe, but forces a copy. | -| | #[buffer] V8Slice | X | ArrayBufferView (resizable=false) | ⚠️ JS may modify the contents of slices obtained from buffer. | +| | #[buffer] V8Slice | X | ArrayBufferView (resizable=false) | ⚠️ JS may modify the contents of slices obtained from buffer. | | | #[buffer(detach)] V8Slice | X | ArrayBufferView (resizable=true,false) | Safe. | -| | #[buffer] V8ResizableSlice | X | ArrayBufferView (resizable=true) | ⚠️ JS may modify the contents of slices obtained from buffer. | -| | #[buffer] JsBuffer | X | ArrayBufferView (resizable=false) | ⚠️ JS may modify the contents of slices obtained from buffer. | +| | #[buffer] V8ResizableSlice | X | ArrayBufferView (resizable=true) | ⚠️ JS may modify the contents of slices obtained from buffer. | +| | #[buffer] JsBuffer | X | ArrayBufferView (resizable=false) | ⚠️ JS may modify the contents of slices obtained from buffer. | | X | #[buffer(detach)] JsBuffer | | ArrayBufferView (resizable=true,false) | Safe. | -| | #[buffer(unsafe)] bytes::Bytes | X | ArrayBufferView (resizable=false) | ⚠️ JS may modify the contents of the buffer. | +| | #[buffer(unsafe)] bytes::Bytes | X | ArrayBufferView (resizable=false) | ⚠️ JS may modify the contents of the buffer. | | | #[buffer(detach)] bytes::Bytes | X | ArrayBufferView (resizable=true,false) | Safe. | | X | *const std::ffi::c_void | X | External | | | X | *mut std::ffi::c_void | X | External | | @@ -63,4 +63,4 @@ | X | #[state] &StateObject | X | | Extracts an object from `OpState`. | | X | #[state] &mut StateObject | X | | Extracts an object from `OpState`. | | X | &JsRuntimeState | X | | Only usable in `deno_core`. | -| X | *mut v8::Isolate | X | | ⚠️ Extremely dangerous, may crash if you don't use `nofast` depending on what you do. | +| X | *mut v8::Isolate | X | | ⚠️ Extremely dangerous, may crash if you don't use `nofast` depending on what you do. | From 882e49d876857b3378fc495291bc14501609a07e Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Fri, 6 Sep 2024 09:35:41 +1000 Subject: [PATCH 2/3] update --- core/ops_metrics.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/core/ops_metrics.rs b/core/ops_metrics.rs index ad2d24ef8..5ee62ef7c 100644 --- a/core/ops_metrics.rs +++ b/core/ops_metrics.rs @@ -1,7 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use crate::ops::OpCtx; -use crate::serde::Serialize; use crate::OpDecl; use crate::OpId; use std::rc::Rc; @@ -92,24 +91,3 @@ pub fn dispatch_metrics_async(opctx: &OpCtx, metrics: OpMetricsEvent) { ) } } - -/// Used for both aggregate and per-op metrics. -#[derive(Clone, Default, Debug, Serialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct OpMetricsSummary { - // The number of ops dispatched synchronously - pub ops_dispatched_sync: u64, - // The number of ops dispatched asynchronously - pub ops_dispatched_async: u64, - // The number of sync ops dispatched fast - pub ops_dispatched_fast: u64, - // The number of asynchronously-dispatch ops completed - pub ops_completed_async: u64, -} - -impl OpMetricsSummary { - /// Does this op have outstanding async op dispatches? - pub fn has_outstanding_ops(&self) -> bool { - self.ops_dispatched_async > self.ops_completed_async - } -} From 456acdeb2354b3b60a799e5ad2f49a96f57459d6 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Mon, 9 Sep 2024 09:37:45 +1000 Subject: [PATCH 3/3] reverts --- core/lib.rs | 2 + core/ops_metrics.rs | 98 +++++++++++++++++++++++++++++++++++++++ core/runtime/tests/ops.rs | 55 ++++++++++++++++++++++ 3 files changed, 155 insertions(+) diff --git a/core/lib.rs b/core/lib.rs index 47b5d4876..dbd510d00 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -142,6 +142,8 @@ pub use crate::ops_metrics::OpMetricsEvent; pub use crate::ops_metrics::OpMetricsFactoryFn; pub use crate::ops_metrics::OpMetricsFn; pub use crate::ops_metrics::OpMetricsSource; +pub use crate::ops_metrics::OpMetricsSummary; +pub use crate::ops_metrics::OpMetricsSummaryTracker; pub use crate::path::strip_unc_prefix; pub use crate::runtime::stats; pub use crate::runtime::CompiledWasmModuleStore; diff --git a/core/ops_metrics.rs b/core/ops_metrics.rs index 5ee62ef7c..fbcd43e23 100644 --- a/core/ops_metrics.rs +++ b/core/ops_metrics.rs @@ -1,8 +1,12 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. use crate::ops::OpCtx; +use crate::serde::Serialize; use crate::OpDecl; use crate::OpId; +use std::cell::Ref; +use std::cell::RefCell; +use std::cell::RefMut; use std::rc::Rc; /// The type of op metrics event. @@ -91,3 +95,97 @@ pub fn dispatch_metrics_async(opctx: &OpCtx, metrics: OpMetricsEvent) { ) } } + +/// Used for both aggregate and per-op metrics. +#[derive(Clone, Default, Debug, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct OpMetricsSummary { + // The number of ops dispatched synchronously + pub ops_dispatched_sync: u64, + // The number of ops dispatched asynchronously + pub ops_dispatched_async: u64, + // The number of sync ops dispatched fast + pub ops_dispatched_fast: u64, + // The number of asynchronously-dispatch ops completed + pub ops_completed_async: u64, +} + +impl OpMetricsSummary { + /// Does this op have outstanding async op dispatches? + pub fn has_outstanding_ops(&self) -> bool { + self.ops_dispatched_async > self.ops_completed_async + } +} + +#[derive(Default, Debug)] +pub struct OpMetricsSummaryTracker { + ops: RefCell>, +} + +impl OpMetricsSummaryTracker { + pub fn per_op(&self) -> Ref<'_, Vec> { + self.ops.borrow() + } + + pub fn aggregate(&self) -> OpMetricsSummary { + let mut sum = OpMetricsSummary::default(); + + for metrics in self.ops.borrow().iter() { + sum.ops_dispatched_sync += metrics.ops_dispatched_sync; + sum.ops_dispatched_fast += metrics.ops_dispatched_fast; + sum.ops_dispatched_async += metrics.ops_dispatched_async; + sum.ops_completed_async += metrics.ops_completed_async; + } + + sum + } + + #[inline] + fn metrics_mut(&self, id: OpId) -> RefMut { + RefMut::map(self.ops.borrow_mut(), |ops| &mut ops[id as usize]) + } + + /// Returns a [`OpMetricsFn`] for this tracker. + fn op_metrics_fn(self: Rc) -> OpMetricsFn { + Rc::new(move |ctx, event, source| match event { + OpMetricsEvent::Dispatched => { + let mut m = self.metrics_mut(ctx.id); + if source == OpMetricsSource::Fast { + m.ops_dispatched_fast += 1; + } + if ctx.decl.is_async { + m.ops_dispatched_async += 1; + } else { + m.ops_dispatched_sync += 1; + } + } + OpMetricsEvent::Completed + | OpMetricsEvent::Error + | OpMetricsEvent::CompletedAsync + | OpMetricsEvent::ErrorAsync => { + if ctx.decl.is_async { + self.metrics_mut(ctx.id).ops_completed_async += 1; + } + } + }) + } + + /// Retrieves the metrics factory function for this tracker. + pub fn op_metrics_factory_fn( + self: Rc, + op_enabled: impl Fn(&OpDecl) -> bool + 'static, + ) -> OpMetricsFactoryFn { + Box::new(move |_, total, op| { + let mut ops = self.ops.borrow_mut(); + if ops.capacity() == 0 { + ops.reserve_exact(total); + } + ops.push(OpMetricsSummary::default()); + if op_enabled(op) { + Some(self.clone().op_metrics_fn()) + } else { + None + } + }) + } +} diff --git a/core/runtime/tests/ops.rs b/core/runtime/tests/ops.rs index 9f5916b92..f0c9be77e 100644 --- a/core/runtime/tests/ops.rs +++ b/core/runtime/tests/ops.rs @@ -685,3 +685,58 @@ op_async_arg_error Dispatched op_async_arg_error Error"# ); } + +#[tokio::test] +pub async fn test_op_metrics_summary_tracker() { + let tracker = Rc::new(OpMetricsSummaryTracker::default()); + // We want to limit the tracker to just the ops we care about + let op_enabled = |op: &OpDecl| { + op.name.starts_with("op_async") || op.name.starts_with("op_sync") + }; + let mut runtime = JsRuntime::new(RuntimeOptions { + extensions: vec![test_ext::init_ops()], + op_metrics_factory_fn: Some( + tracker.clone().op_metrics_factory_fn(op_enabled), + ), + ..Default::default() + }); + + let promise = runtime + .execute_script( + "filename.js", + r#" + const { op_sync, op_sync_error, op_async, op_async_error, op_async_yield, op_async_yield_error, op_async_deferred, op_async_lazy, op_async_impl_future_error, op_sync_arg_error, op_async_arg_error } = Deno.core.ops; + async function go() { + op_sync(); + try { op_sync_error(); } catch {} + await op_async(); + try { await op_async_error() } catch {} + await op_async_yield(); + try { await op_async_yield_error() } catch {} + await op_async_deferred(); + await op_async_lazy(); + try { await op_async_impl_future_error() } catch {} + try { op_sync_arg_error() } catch {} + try { await op_async_arg_error() } catch {} + } + + go() + "#, + ) + .unwrap(); + #[allow(deprecated)] + runtime + .resolve_value(promise) + .await + .expect("Failed to await promise"); + drop(runtime); + assert_eq!( + tracker.aggregate(), + OpMetricsSummary { + ops_completed_async: 8, + ops_dispatched_async: 8, + ops_dispatched_sync: 3, + ops_dispatched_fast: 0, + } + ); +}