2323
2424use std:: collections:: HashMap ;
2525use std:: ops:: ControlFlow ;
26+ use std:: panic:: AssertUnwindSafe ;
2627use std:: sync:: Mutex ;
2728
2829use axum:: {
@@ -32,6 +33,7 @@ use axum::{
3233 routing:: { get, post} ,
3334} ;
3435use criterion:: { BenchmarkId , Criterion , Throughput , criterion_group, criterion_main} ;
36+ use futures_util:: FutureExt ;
3537use serde:: { Deserialize , Serialize } ;
3638use tokio:: runtime:: Runtime ;
3739use vespera_inprocess:: {
@@ -485,6 +487,61 @@ fn bench_streaming_path(c: &mut Criterion) {
485487 drop ( runtime) ;
486488}
487489
490+ /// #2 isolation: the `vespera_jni::dispatchAsync` spawn mechanism.
491+ ///
492+ /// Both variants run the dispatch task on a shared multi-thread runtime
493+ /// (the outer `tokio::spawn`, common to both) and differ only in how a
494+ /// panic in the dispatch future is isolated:
495+ ///
496+ /// - `double_spawn_pre`: a **second** `tokio::spawn` (panic → `JoinError`),
497+ /// the pre-#2 shape — one extra task allocation + scheduler hop.
498+ /// - `single_spawn_catch_unwind_post`: `FutureExt::catch_unwind` in place,
499+ /// the post-#2 shape — same panic → fallback, no second task.
500+ ///
501+ /// The inner future is trivial so the spawn/catch_unwind overhead is the
502+ /// dominant cost and the delta isolates exactly what #2 removes per async
503+ /// dispatch (independent of the dispatch payload size).
504+ fn bench_async_spawn_pattern ( c : & mut Criterion ) {
505+ let runtime = tokio:: runtime:: Builder :: new_multi_thread ( )
506+ . worker_threads ( 4 )
507+ . enable_all ( )
508+ . build ( )
509+ . expect ( "multi-thread runtime" ) ;
510+ let mut group = c. benchmark_group ( "async_spawn_pattern" ) ;
511+
512+ group. bench_function ( "double_spawn_pre" , |b| {
513+ b. iter ( || {
514+ runtime. block_on ( async {
515+ tokio:: spawn ( async move {
516+ tokio:: spawn ( async { vec ! [ 0u8 ; 64 ] } )
517+ . await
518+ . unwrap_or_else ( |_| vec ! [ 1u8 ; 16 ] )
519+ } )
520+ . await
521+ . unwrap ( )
522+ } )
523+ } ) ;
524+ } ) ;
525+
526+ group. bench_function ( "single_spawn_catch_unwind_post" , |b| {
527+ b. iter ( || {
528+ runtime. block_on ( async {
529+ tokio:: spawn ( async move {
530+ AssertUnwindSafe ( async { vec ! [ 0u8 ; 64 ] } )
531+ . catch_unwind ( )
532+ . await
533+ . unwrap_or_else ( |_| vec ! [ 1u8 ; 16 ] )
534+ } )
535+ . await
536+ . unwrap ( )
537+ } )
538+ } ) ;
539+ } ) ;
540+
541+ group. finish ( ) ;
542+ drop ( runtime) ;
543+ }
544+
488545criterion_group ! (
489546 benches,
490547 bench_router_path,
@@ -493,6 +550,7 @@ criterion_group!(
493550 bench_resolve_path,
494551 bench_contended_path,
495552 bench_headers_path,
496- bench_streaming_path
553+ bench_streaming_path,
554+ bench_async_spawn_pattern
497555) ;
498556criterion_main ! ( benches) ;
0 commit comments