@@ -3405,18 +3405,35 @@ pub async fn account_delete_device(targetDeviceId: String) -> Result<(), String>
34053405/// which routes it to the target device's WS. The target executes it
34063406/// and the response is returned (decrypted).
34073407/// Returns the decrypted response JSON.
3408+ const ACCOUNT_DEVICE_RPC_DEFAULT_TIMEOUT_MS : u64 = 120_000 ;
3409+ const ACCOUNT_DEVICE_RPC_MIN_TIMEOUT_MS : u64 = 1_000 ;
3410+
3411+ fn account_device_rpc_timeout_ms ( requested : Option < u64 > ) -> u64 {
3412+ requested
3413+ . unwrap_or ( ACCOUNT_DEVICE_RPC_DEFAULT_TIMEOUT_MS )
3414+ . clamp (
3415+ ACCOUNT_DEVICE_RPC_MIN_TIMEOUT_MS ,
3416+ ACCOUNT_DEVICE_RPC_DEFAULT_TIMEOUT_MS ,
3417+ )
3418+ }
3419+
34083420#[ tauri:: command]
34093421pub async fn account_device_rpc (
34103422 target_device_id : String ,
34113423 command_json : String ,
3424+ timeout_ms : Option < u64 > ,
34123425) -> Result < String , String > {
34133426 let account_generation = account_context_generation ( ) ;
34143427 let ( session, relay_url) = read_account_context_for_generation ( account_generation) . await ?;
34153428 let client = AccountClient :: new ( ) ;
3416- let response = client
3417- . device_rpc ( & relay_url, & session, & target_device_id, & command_json)
3418- . await
3419- . map_err ( |e| format ! ( "{e}" ) ) ?;
3429+ let timeout_ms = account_device_rpc_timeout_ms ( timeout_ms) ;
3430+ let response = tokio:: time:: timeout (
3431+ std:: time:: Duration :: from_millis ( timeout_ms) ,
3432+ client. device_rpc ( & relay_url, & session, & target_device_id, & command_json) ,
3433+ )
3434+ . await
3435+ . map_err ( |_| format ! ( "device RPC timed out after {timeout_ms}ms" ) ) ?
3436+ . map_err ( |e| format ! ( "{e}" ) ) ?;
34203437 if !account_context_matches ( account_generation, & session. token ) . await {
34213438 return Err ( "account context changed" . to_string ( ) ) ;
34223439 }
@@ -3706,58 +3723,56 @@ async fn account_auto_sync_inner(
37063723 ) ;
37073724
37083725 let completed = std:: sync:: Arc :: new ( std:: sync:: atomic:: AtomicUsize :: new ( 0 ) ) ;
3709- let upload_outcomes: Vec < Result < ( String , String , i64 ) , String > > =
3710- stream:: iter ( pending_uploads)
3711- . map ( |( session_id, bundle_json, hash) | {
3712- let client = AccountClient :: new ( ) ;
3713- let relay_url = relay_url. clone ( ) ;
3714- let acct_session = acct_session. clone ( ) ;
3715- let completed = completed. clone ( ) ;
3716- async move {
3717- if ensure_account_auto_sync_current ( sync_operation_id) . is_err ( ) {
3718- return Err ( "account sync cancelled" . to_string ( ) ) ;
3719- }
3720- let result = match await_account_auto_sync (
3721- sync_operation_id,
3722- client. upload_session ( & relay_url, & acct_session, & session_id, & bundle_json) ,
3723- )
3724- . await
3725- {
3726- Ok ( result) => result,
3727- Err ( e) => return Err ( e) ,
3728- } ;
3729- match result {
3730- Ok ( version) => {
3731- let done =
3732- completed. fetch_add ( 1 , std:: sync:: atomic:: Ordering :: Relaxed ) + 1 ;
3733- let percent = if upload_total == 0 {
3734- 95u8
3735- } else {
3736- 20 + ( ( 75 * done) / upload_total) as u8
3737- } ;
3738- if ensure_account_auto_sync_current ( sync_operation_id) . is_err ( ) {
3739- return Err ( "account sync cancelled" . to_string ( ) ) ;
3740- }
3741- emit_sync_progress (
3742- sync_operation_id,
3743- "exporting_sessions" ,
3744- percent. min ( 95 ) ,
3745- Some ( done) ,
3746- Some ( upload_total) ,
3747- Some ( session_id. as_str ( ) ) ,
3748- ) ;
3749- Ok ( ( session_id, hash, version) )
3750- }
3751- Err ( e) => {
3752- log:: warn!( "Auto-sync upload {session_id} failed: {e}" ) ;
3753- Err ( format ! ( "{session_id}: {e}" ) )
3726+ let upload_outcomes: Vec < Result < ( String , String , i64 ) , String > > = stream:: iter ( pending_uploads)
3727+ . map ( |( session_id, bundle_json, hash) | {
3728+ let client = AccountClient :: new ( ) ;
3729+ let relay_url = relay_url. clone ( ) ;
3730+ let acct_session = acct_session. clone ( ) ;
3731+ let completed = completed. clone ( ) ;
3732+ async move {
3733+ if ensure_account_auto_sync_current ( sync_operation_id) . is_err ( ) {
3734+ return Err ( "account sync cancelled" . to_string ( ) ) ;
3735+ }
3736+ let result = match await_account_auto_sync (
3737+ sync_operation_id,
3738+ client. upload_session ( & relay_url, & acct_session, & session_id, & bundle_json) ,
3739+ )
3740+ . await
3741+ {
3742+ Ok ( result) => result,
3743+ Err ( e) => return Err ( e) ,
3744+ } ;
3745+ match result {
3746+ Ok ( version) => {
3747+ let done = completed. fetch_add ( 1 , std:: sync:: atomic:: Ordering :: Relaxed ) + 1 ;
3748+ let percent = if upload_total == 0 {
3749+ 95u8
3750+ } else {
3751+ 20 + ( ( 75 * done) / upload_total) as u8
3752+ } ;
3753+ if ensure_account_auto_sync_current ( sync_operation_id) . is_err ( ) {
3754+ return Err ( "account sync cancelled" . to_string ( ) ) ;
37543755 }
3756+ emit_sync_progress (
3757+ sync_operation_id,
3758+ "exporting_sessions" ,
3759+ percent. min ( 95 ) ,
3760+ Some ( done) ,
3761+ Some ( upload_total) ,
3762+ Some ( session_id. as_str ( ) ) ,
3763+ ) ;
3764+ Ok ( ( session_id, hash, version) )
3765+ }
3766+ Err ( e) => {
3767+ log:: warn!( "Auto-sync upload {session_id} failed: {e}" ) ;
3768+ Err ( format ! ( "{session_id}: {e}" ) )
37553769 }
37563770 }
3757- } )
3758- . buffer_unordered ( UPLOAD_CONCURRENCY )
3759- . collect ( )
3760- . await ;
3771+ }
3772+ } )
3773+ . buffer_unordered ( UPLOAD_CONCURRENCY )
3774+ . collect ( )
3775+ . await ;
37613776
37623777 ensure_account_auto_sync_current ( sync_operation_id) ?;
37633778
@@ -4346,6 +4361,23 @@ mod sync_state_tests {
43464361 ) ;
43474362 }
43484363
4364+ #[ test]
4365+ fn device_rpc_timeout_is_bounded_for_peer_requests ( ) {
4366+ assert_eq ! (
4367+ account_device_rpc_timeout_ms( None ) ,
4368+ ACCOUNT_DEVICE_RPC_DEFAULT_TIMEOUT_MS
4369+ ) ;
4370+ assert_eq ! ( account_device_rpc_timeout_ms( Some ( 10_000 ) ) , 10_000 ) ;
4371+ assert_eq ! (
4372+ account_device_rpc_timeout_ms( Some ( 100 ) ) ,
4373+ ACCOUNT_DEVICE_RPC_MIN_TIMEOUT_MS
4374+ ) ;
4375+ assert_eq ! (
4376+ account_device_rpc_timeout_ms( Some ( u64 :: MAX ) ) ,
4377+ ACCOUNT_DEVICE_RPC_DEFAULT_TIMEOUT_MS
4378+ ) ;
4379+ }
4380+
43494381 #[ test]
43504382 fn login_result_exposes_only_an_opaque_pending_owner ( ) {
43514383 let value = serde_json:: to_value ( AccountLoginResult {
0 commit comments