commit 519afbb6c25f02aa33b9f05f39639fb3459445ee
parent 8d63f0086c607ba3f7d02e1aca2b65c9f80c0c2e
Author: triesap <tyson@radroots.org>
Date: Tue, 4 Aug 2026 07:58:17 +0000
runtime: bind actor waits to native scheduler
- execute mailbox receipt timeouts on the owned Tokio runtime
- preserve a safe closed result when a scheduled wait terminates
- isolate native JVM tests behind a development-only data root
- cover generated recovery through the real Kotlin and UniFFI boundary
Diffstat:
2 files changed, 16 insertions(+), 5 deletions(-)
diff --git a/crates/studio_ffi/src/commands.rs b/crates/studio_ffi/src/commands.rs
@@ -23,6 +23,7 @@ const DATABASE_QUALIFIER: &str = "org";
const DATABASE_ORGANIZATION: &str = "radroots";
const DATABASE_APPLICATION: &str = "studio";
const DATABASE_FILENAME: &str = "studio.sqlite3";
+const DEVELOPMENT_DATA_DIR_ENVIRONMENT: &str = "RADROOTS_STUDIO_DEVELOPMENT_DATA_DIR";
pub(crate) const ACTOR_MAILBOX_CAPACITY: usize = 64;
pub const FFI_CONTRACT_MAJOR: u16 = 2;
pub const FFI_CONTRACT_MINOR: u16 = 0;
@@ -229,7 +230,7 @@ impl StudioAppCore {
expectation: CompatibilityExpectation,
development_mode: bool,
) -> Result<Arc<Self>, StudioError> {
- let path = canonical_database_path()?;
+ let path = application_database_path(development_mode)?;
Self::open_path_compatible(&path, &expectation, development_mode)
}
@@ -540,7 +541,11 @@ impl Clock for SystemClock {
}
}
-fn canonical_database_path() -> Result<PathBuf, StudioError> {
+fn application_database_path(development_mode: bool) -> Result<PathBuf, StudioError> {
+ if development_mode && let Some(directory) = std::env::var_os(DEVELOPMENT_DATA_DIR_ENVIRONMENT)
+ {
+ return Ok(PathBuf::from(directory).join(DATABASE_FILENAME));
+ }
ProjectDirs::from(
DATABASE_QUALIFIER,
DATABASE_ORGANIZATION,
diff --git a/crates/studio_storage/src/runtime_actor.rs b/crates/studio_storage/src/runtime_actor.rs
@@ -115,6 +115,7 @@ pub struct RuntimeActorHandle {
mailbox: ActorMailbox<RuntimeCommand, RuntimeCommandValue>,
adapter: Arc<PersistentAppCore>,
lifecycle: Arc<Mutex<LifecycleGate>>,
+ runtime: Handle,
next_request: Arc<AtomicU64>,
session_generation: Arc<AtomicU64>,
foreground_session: Arc<Mutex<Option<ForegroundSessionBinding>>>,
@@ -234,6 +235,7 @@ impl RuntimeActorHandle {
mailbox,
adapter,
lifecycle,
+ runtime: runtime.clone(),
next_request: Arc::new(AtomicU64::new(1)),
session_generation,
foreground_session,
@@ -560,9 +562,13 @@ impl RuntimeActorHandle {
let receipt = match self.mailbox.submit(context, command) {
CommandSubmission::Accepted(ticket) => {
let remaining = deadline.saturating_duration_since(Instant::now());
- match tokio::time::timeout(remaining, ticket.receipt()).await {
- Ok(receipt) => receipt,
- Err(_) => CommandReceipt::new(request_id, CommandResult::TimedOut),
+ let waiting = self
+ .runtime
+ .spawn(async move { tokio::time::timeout(remaining, ticket.receipt()).await });
+ match waiting.await {
+ Ok(Ok(receipt)) => receipt,
+ Ok(Err(_)) => CommandReceipt::new(request_id, CommandResult::TimedOut),
+ Err(_) => CommandReceipt::new(request_id, CommandResult::Closed),
}
}
CommandSubmission::Rejected(receipt) => receipt,