diff --git a/releases/rust/esdk/src/deps/com_amazonaws_dynamodb/client.rs b/releases/rust/esdk/src/deps/com_amazonaws_dynamodb/client.rs index 56f13bf93..2cacfdb1f 100644 --- a/releases/rust/esdk/src/deps/com_amazonaws_dynamodb/client.rs +++ b/releases/rust/esdk/src/deps/com_amazonaws_dynamodb/client.rs @@ -4,7 +4,6 @@ use std::future::Future; use tokio::runtime::Builder; use tokio::runtime::Handle; -use tokio::runtime::RuntimeFlavor; pub fn escape_to_async(fut: F) -> O where @@ -12,20 +11,26 @@ where O: Send, { match Handle::try_current() { - Ok(handle) => match handle.runtime_flavor() { - RuntimeFlavor::CurrentThread => std::thread::scope(move |t| { - t.spawn(move || { - Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(fut) - }) - .join() - .unwrap() - }), - _ => tokio::task::block_in_place(move || handle.block_on(fut)), - }, + // Any ambient runtime: run the future on a scoped thread with its own + // runtime. Blocking the calling thread is what a sync bridge does, but + // the future must never depend on the caller's runtime for progress: + // `block_in_place` + `Handle::block_on` parks a worker on a future + // that same runtime has to drive, and when that worker is the last + // one awake (the shared IO/timer driver unowned, all other workers + // parked) the future can never complete and the whole runtime + // deadlocks permanently. A fresh current-thread runtime drives the + // future independently on every flavor. + Ok(_) => std::thread::scope(move |t| { + t.spawn(move || { + Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(fut) + }) + .join() + .unwrap() + }), Err(_) => Builder::new_current_thread() .enable_all() .build() diff --git a/releases/rust/esdk/src/deps/com_amazonaws_kms/client.rs b/releases/rust/esdk/src/deps/com_amazonaws_kms/client.rs index 257df0e3f..e54517b46 100644 --- a/releases/rust/esdk/src/deps/com_amazonaws_kms/client.rs +++ b/releases/rust/esdk/src/deps/com_amazonaws_kms/client.rs @@ -4,7 +4,6 @@ use std::future::Future; use tokio::runtime::Builder; use tokio::runtime::Handle; -use tokio::runtime::RuntimeFlavor; pub fn escape_to_async(fut: F) -> O where @@ -12,20 +11,26 @@ where O: Send, { match Handle::try_current() { - Ok(handle) => match handle.runtime_flavor() { - RuntimeFlavor::CurrentThread => std::thread::scope(move |t| { - t.spawn(move || { - Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(fut) - }) - .join() - .unwrap() - }), - _ => tokio::task::block_in_place(move || handle.block_on(fut)), - }, + // Any ambient runtime: run the future on a scoped thread with its own + // runtime. Blocking the calling thread is what a sync bridge does, but + // the future must never depend on the caller's runtime for progress: + // `block_in_place` + `Handle::block_on` parks a worker on a future + // that same runtime has to drive, and when that worker is the last + // one awake (the shared IO/timer driver unowned, all other workers + // parked) the future can never complete and the whole runtime + // deadlocks permanently. A fresh current-thread runtime drives the + // future independently on every flavor. + Ok(_) => std::thread::scope(move |t| { + t.spawn(move || { + Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(fut) + }) + .join() + .unwrap() + }), Err(_) => Builder::new_current_thread() .enable_all() .build() diff --git a/releases/rust/esdk/src/escape.rs b/releases/rust/esdk/src/escape.rs index 414aed3e8..153d31e63 100644 --- a/releases/rust/esdk/src/escape.rs +++ b/releases/rust/esdk/src/escape.rs @@ -4,7 +4,6 @@ use std::future::Future; use tokio::runtime::Builder; use tokio::runtime::Handle; -use tokio::runtime::RuntimeFlavor; pub(crate) fn escape_to_async(fut: F) -> O where @@ -12,20 +11,26 @@ where O: Send, { match Handle::try_current() { - Ok(handle) => match handle.runtime_flavor() { - RuntimeFlavor::CurrentThread => std::thread::scope(move |t| { - t.spawn(move || { - Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(fut) - }) - .join() - .unwrap() - }), - _ => tokio::task::block_in_place(move || handle.block_on(fut)), - }, + // Any ambient runtime: run the future on a scoped thread with its own + // runtime. Blocking the calling thread is what a sync bridge does, but + // the future must never depend on the caller's runtime for progress: + // `block_in_place` + `Handle::block_on` parks a worker on a future + // that same runtime has to drive, and when that worker is the last + // one awake (the shared IO/timer driver unowned, all other workers + // parked) the future can never complete and the whole runtime + // deadlocks permanently. A fresh current-thread runtime drives the + // future independently on every flavor. + Ok(_) => std::thread::scope(move |t| { + t.spawn(move || { + Builder::new_current_thread() + .enable_all() + .build() + .unwrap() + .block_on(fut) + }) + .join() + .unwrap() + }), Err(_) => Builder::new_current_thread() .enable_all() .build()