diff --git a/ic-cdk-bindgen/src/lib.rs b/ic-cdk-bindgen/src/lib.rs index 7d4ce5e67..114a1e677 100644 --- a/ic-cdk-bindgen/src/lib.rs +++ b/ic-cdk-bindgen/src/lib.rs @@ -31,7 +31,7 @@ pub struct Config { canister_name: String, candid_path: PathBuf, mode: Mode, - type_selector_config_path: Option, // TODO: Implement type selector config + type_selector_config_path: Option, } /// Bindgen mode. @@ -155,8 +155,14 @@ impl Config { e ) }); - // unused are not handled - let (output, _unused) = emit_bindgen(&rust_bindgen_config, &env, &actor, &prog); + let (output, unused) = emit_bindgen(&rust_bindgen_config, &env, &actor, &prog); + if !unused.is_empty() { + println!( + "cargo:warning=ic-cdk-bindgen: {} unused type(s) were not emitted: {:?}", + unused.len(), + unused + ); + } // 2. Generate the Rust bindings using the Handlebars template let mut external = ExternalConfig::default(); diff --git a/ic-cdk-timers/src/global_timer.rs b/ic-cdk-timers/src/global_timer.rs index 143741950..2091ed465 100644 --- a/ic-cdk-timers/src/global_timer.rs +++ b/ic-cdk-timers/src/global_timer.rs @@ -12,6 +12,7 @@ fn reschedule_timer(timers: &mut BinaryHeap, id: TaskId, base: u64, inter task: id, time, counter: state::next_counter(), + backoff_secs: 0, }); } None => ic0::debug_print( @@ -49,7 +50,7 @@ extern "C" fn global_timer() { first = false; } else if insn_count == 0 { insn_count = ic0::performance_counter(0); - } else if insn_count * 3 + ic0::performance_counter(0) > 40_000_000_000 { + } else if insn_count.saturating_mul(3).saturating_add(ic0::performance_counter(0)) > 40_000_000_000 { ic0::debug_print( b"[ic-cdk-timers] canister_global_timer: approaching instruction limit, \ deferring remaining timers to next round", @@ -143,7 +144,7 @@ fn do_timer( // here is performing an inter-canister call to ourselves; traps will be caught at the call // boundary. This invokes a meaningful cycles cost, and should an alternative for catching traps // become available, this code should be rewritten. - let env = Box::new(CallEnv { + let mut env = Box::new(CallEnv { timer, method_handle: ic_cdk_executor::extend_current_method_context(), }); @@ -153,7 +154,19 @@ fn do_timer( let cost = ic0::cost_call(METHOD_NAME.len() as u64, 8); // --- no allocations between the liquid cycles check and call_perform if liquid_cycles < cost { - ic0::debug_print(b"[ic-cdk-timers] unable to schedule timer: not enough liquid cycles"); + let next_backoff_secs = match env.timer.backoff_secs { + 0 => 5, + n => (n * 2).min(60), + }; + ic0::debug_print( + format!( + "[ic-cdk-timers] unable to schedule timer: not enough liquid cycles, \ + retrying in {next_backoff_secs}s" + ) + .as_bytes(), + ); + env.timer.time = now.saturating_add(next_backoff_secs as u64 * 1_000_000_000); + env.timer.backoff_secs = next_backoff_secs; return ControlFlow::Break(Some(env.timer)); } let env = Box::::into_raw(env) as usize; diff --git a/ic-cdk-timers/src/lib.rs b/ic-cdk-timers/src/lib.rs index e05106aed..ea391c9ae 100644 --- a/ic-cdk-timers/src/lib.rs +++ b/ic-cdk-timers/src/lib.rs @@ -71,6 +71,7 @@ pub fn set_timer(delay: Duration, future: impl Future + 'static) -> task: key, time: scheduled_time, counter: state::next_counter(), + backoff_secs: 0, }) }); state::update_ic0_timer(); @@ -127,6 +128,7 @@ where task: key, time: scheduled_time, counter: state::next_counter(), + backoff_secs: 0, }); }); state::update_ic0_timer(); @@ -177,6 +179,7 @@ pub fn set_timer_interval_serial(interval: Duration, func: impl AsyncFnMut() + ' task: key, time: scheduled_time, counter: state::next_counter(), + backoff_secs: 0, }); }); state::update_ic0_timer(); diff --git a/ic-cdk-timers/src/state.rs b/ic-cdk-timers/src/state.rs index ec85b6953..7f0e8682c 100644 --- a/ic-cdk-timers/src/state.rs +++ b/ic-cdk-timers/src/state.rs @@ -57,6 +57,7 @@ pub(crate) struct Timer { pub(crate) task: TaskId, pub(crate) time: u64, pub(crate) counter: u128, + pub(crate) backoff_secs: u32, } // Timers are sorted first by time, then by insertion order to ensure deterministic ordering. diff --git a/ic-cdk/src/stable.rs b/ic-cdk/src/stable.rs index 096f10744..0f431f547 100644 --- a/ic-cdk/src/stable.rs +++ b/ic-cdk/src/stable.rs @@ -186,7 +186,10 @@ impl StableIO { /// /// When it cannot grow the memory to accommodate the new data. pub fn write(&mut self, buf: &[u8]) -> Result { - let required_capacity_bytes = self.offset + buf.len() as u64; + let required_capacity_bytes = self + .offset + .checked_add(buf.len() as u64) + .ok_or(StableMemoryError::OutOfBounds)?; let required_capacity_pages = required_capacity_bytes.div_ceil(WASM_PAGE_SIZE_IN_BYTES); let current_pages = self.capacity; let additional_pages_required = required_capacity_pages.saturating_sub(current_pages); @@ -233,9 +236,23 @@ impl StableIO { self.offset = match offset { io::SeekFrom::Start(offset) => offset, io::SeekFrom::End(offset) => { - ((self.capacity * WASM_PAGE_SIZE_IN_BYTES) as i64 + offset) as u64 + let end = i64::try_from(self.capacity * WASM_PAGE_SIZE_IN_BYTES) + .ok() + .and_then(|end| end.checked_add(offset)) + .and_then(|pos| u64::try_from(pos).ok()); + end.ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidInput, "seek position overflow") + })? + } + io::SeekFrom::Current(offset) => { + let pos = i64::try_from(self.offset) + .ok() + .and_then(|cur| cur.checked_add(offset)) + .and_then(|pos| u64::try_from(pos).ok()); + pos.ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidInput, "seek position overflow") + })? } - io::SeekFrom::Current(offset) => (self.offset as i64 + offset) as u64, }; Ok(self.offset)