@@ -60,7 +60,7 @@ use ic_types::{
60
60
use ic_wasm_transform:: Module ;
61
61
use ic_wasm_types:: { doc_ref, AsErrorHelp , CanisterModule , ErrorHelp , WasmHash } ;
62
62
use num_traits:: cast:: ToPrimitive ;
63
- use num_traits:: SaturatingAdd ;
63
+ use num_traits:: { SaturatingAdd , SaturatingSub } ;
64
64
use prometheus:: IntCounter ;
65
65
use serde:: { Deserialize , Serialize } ;
66
66
use std:: path:: PathBuf ;
@@ -1790,6 +1790,94 @@ impl CanisterManager {
1790
1790
Ok ( StoredChunksReply ( keys) )
1791
1791
}
1792
1792
1793
+ // Runs the following checks on memory usage and return an error
1794
+ // if any fails:
1795
+ // 1. Check new usage will not freeze canister
1796
+ // 2. Check subnet has available memory
1797
+
1798
+ // Additionally calculates if any cycles will need to be reserved.
1799
+ //
1800
+ // This is to be used when taking or loading a canister snapshot
1801
+ // to ensure consistency in checks.
1802
+ fn memory_usage_checks (
1803
+ & self ,
1804
+ subnet_size : usize ,
1805
+ canister : & mut CanisterState ,
1806
+ round_limits : & RoundLimits ,
1807
+ new_memory_usage : NumBytes ,
1808
+ old_memory_usage : NumBytes ,
1809
+ resource_saturation : & ResourceSaturation ,
1810
+ ) -> Result < ( ) , CanisterManagerError > {
1811
+ let memory_increase = new_memory_usage. saturating_sub ( & old_memory_usage) ;
1812
+
1813
+ let reservation_cycles = self . cycles_account_manager . storage_reservation_cycles (
1814
+ memory_increase,
1815
+ resource_saturation,
1816
+ subnet_size,
1817
+ ) ;
1818
+
1819
+ // Check that the canister does not exceed its freezing threshold based
1820
+ // on the new memory usage.
1821
+ let threshold = self . cycles_account_manager . freeze_threshold_cycles (
1822
+ canister. system_state . freeze_threshold ,
1823
+ canister. memory_allocation ( ) ,
1824
+ new_memory_usage,
1825
+ canister. message_memory_usage ( ) ,
1826
+ canister. compute_allocation ( ) ,
1827
+ subnet_size,
1828
+ canister. system_state . reserved_balance ( ) ,
1829
+ ) ;
1830
+
1831
+ if canister. system_state . balance ( ) < threshold + reservation_cycles {
1832
+ return Err ( CanisterManagerError :: InsufficientCyclesInMemoryGrow {
1833
+ bytes : memory_increase,
1834
+ available : canister. system_state . balance ( ) ,
1835
+ required : threshold + reservation_cycles,
1836
+ } ) ;
1837
+ }
1838
+
1839
+ // Verify that the subnet has enough memory available to satisfy the
1840
+ // requested change by the canister.
1841
+ round_limits
1842
+ . subnet_available_memory
1843
+ . check_available_memory ( memory_increase, NumBytes :: from ( 0 ) , NumBytes :: from ( 0 ) )
1844
+ . map_err (
1845
+ |_| CanisterManagerError :: SubnetMemoryCapacityOverSubscribed {
1846
+ requested : memory_increase,
1847
+ available : NumBytes :: from (
1848
+ round_limits
1849
+ . subnet_available_memory
1850
+ . get_execution_memory ( )
1851
+ . max ( 0 ) as u64 ,
1852
+ ) ,
1853
+ } ,
1854
+ ) ?;
1855
+
1856
+ // Reserve needed cycles if the subnet is becoming saturated.
1857
+ canister
1858
+ . system_state
1859
+ . reserve_cycles ( reservation_cycles)
1860
+ . map_err ( |err| match err {
1861
+ ReservationError :: InsufficientCycles {
1862
+ requested,
1863
+ available,
1864
+ } => CanisterManagerError :: InsufficientCyclesInMemoryGrow {
1865
+ bytes : memory_increase,
1866
+ available,
1867
+ required : requested,
1868
+ } ,
1869
+ ReservationError :: ReservedLimitExceed { requested, limit } => {
1870
+ CanisterManagerError :: ReservedCyclesLimitExceededInMemoryGrow {
1871
+ bytes : memory_increase,
1872
+ requested,
1873
+ limit,
1874
+ }
1875
+ }
1876
+ } ) ?;
1877
+
1878
+ Ok ( ( ) )
1879
+ }
1880
+
1793
1881
/// Creates a new canister snapshot.
1794
1882
///
1795
1883
/// A canister snapshot can only be initiated by the controllers.
@@ -1880,98 +1968,21 @@ impl CanisterManager {
1880
1968
}
1881
1969
1882
1970
let new_snapshot_size = canister. snapshot_size_bytes ( ) ;
1883
- let new_snapshot_increase = NumBytes :: from (
1884
- new_snapshot_size
1885
- . get ( )
1886
- . saturating_sub ( replace_snapshot_size. get ( ) ) ,
1887
- ) ;
1888
- let new_memory_usage = NumBytes :: from (
1889
- canister
1890
- . memory_usage ( )
1891
- . get ( )
1892
- . saturating_add ( new_snapshot_size. get ( ) )
1893
- . saturating_sub ( replace_snapshot_size. get ( ) ) ,
1894
- ) ;
1895
1971
1896
- {
1897
- // Run the following checks on memory usage and return an error
1898
- // if any fails:
1899
- // 1. Check new usage will not freeze canister
1900
- // 2. Check subnet has available memory
1901
- // 3. Reserve cycles on canister
1902
- // 4. Actually deduct memory from subnet (asserting it won't fail)
1903
-
1904
- // Calculate if any cycles will need to be reserved.
1905
- let reservation_cycles = self . cycles_account_manager . storage_reservation_cycles (
1906
- new_snapshot_increase,
1907
- resource_saturation,
1908
- subnet_size,
1909
- ) ;
1910
-
1911
- // Memory usage will increase by the snapshot size.
1912
- // Check that it doesn't bump the canister over the freezing threshold.
1913
- let threshold = self . cycles_account_manager . freeze_threshold_cycles (
1914
- canister. system_state . freeze_threshold ,
1915
- canister. memory_allocation ( ) ,
1916
- new_memory_usage,
1917
- canister. message_memory_usage ( ) ,
1918
- canister. compute_allocation ( ) ,
1919
- subnet_size,
1920
- canister. system_state . reserved_balance ( ) ,
1921
- ) ;
1922
-
1923
- if canister. system_state . balance ( ) < threshold + reservation_cycles {
1924
- return (
1925
- Err ( CanisterManagerError :: InsufficientCyclesInMemoryGrow {
1926
- bytes : new_snapshot_increase,
1927
- available : canister. system_state . balance ( ) ,
1928
- required : threshold + reservation_cycles,
1929
- } ) ,
1930
- NumInstructions :: new ( 0 ) ,
1931
- ) ;
1932
- }
1933
- // Verify that the subnet has enough memory for a new snapshot.
1934
- if let Err ( err) = round_limits
1935
- . subnet_available_memory
1936
- . check_available_memory ( new_snapshot_increase, NumBytes :: from ( 0 ) , NumBytes :: from ( 0 ) )
1937
- . map_err (
1938
- |_| CanisterManagerError :: SubnetMemoryCapacityOverSubscribed {
1939
- requested : new_snapshot_increase,
1940
- available : NumBytes :: from (
1941
- round_limits
1942
- . subnet_available_memory
1943
- . get_execution_memory ( )
1944
- . max ( 0 ) as u64 ,
1945
- ) ,
1946
- } ,
1947
- )
1948
- {
1949
- return ( Err ( err) , NumInstructions :: new ( 0 ) ) ;
1950
- } ;
1951
- // Reserve needed cycles if the subnet is becoming saturated.
1952
- if let Err ( err) = canister
1953
- . system_state
1954
- . reserve_cycles ( reservation_cycles)
1955
- . map_err ( |err| match err {
1956
- ReservationError :: InsufficientCycles {
1957
- requested,
1958
- available,
1959
- } => CanisterManagerError :: InsufficientCyclesInMemoryGrow {
1960
- bytes : new_snapshot_increase,
1961
- available,
1962
- required : requested,
1963
- } ,
1964
- ReservationError :: ReservedLimitExceed { requested, limit } => {
1965
- CanisterManagerError :: ReservedCyclesLimitExceededInMemoryGrow {
1966
- bytes : new_snapshot_increase,
1967
- requested,
1968
- limit,
1969
- }
1970
- }
1971
- } )
1972
- {
1973
- return ( Err ( err) , NumInstructions :: new ( 0 ) ) ;
1974
- } ;
1972
+ let old_memory_usage = canister. memory_usage ( ) ;
1973
+ let new_memory_usage = canister
1974
+ . memory_usage ( )
1975
+ . saturating_add ( & new_snapshot_size)
1976
+ . saturating_sub ( & replace_snapshot_size) ;
1977
+ if let Err ( err) = self . memory_usage_checks (
1978
+ subnet_size,
1979
+ canister,
1980
+ round_limits,
1981
+ new_memory_usage,
1982
+ old_memory_usage,
1983
+ resource_saturation,
1984
+ ) {
1985
+ return ( Err ( err) , NumInstructions :: from ( 0 ) ) ;
1975
1986
}
1976
1987
1977
1988
// Charge for taking a snapshot of the canister.
@@ -2068,11 +2079,12 @@ impl CanisterManager {
2068
2079
& self ,
2069
2080
subnet_size : usize ,
2070
2081
sender : PrincipalId ,
2071
- canister : & CanisterState ,
2082
+ canister : & mut CanisterState ,
2072
2083
snapshot_id : SnapshotId ,
2073
2084
state : & mut ReplicatedState ,
2074
2085
round_limits : & mut RoundLimits ,
2075
2086
origin : CanisterChangeOrigin ,
2087
+ resource_saturation : & ResourceSaturation ,
2076
2088
long_execution_already_in_progress : & IntCounter ,
2077
2089
) -> ( Result < CanisterState , CanisterManagerError > , NumInstructions ) {
2078
2090
let canister_id = canister. canister_id ( ) ;
@@ -2140,6 +2152,7 @@ impl CanisterManager {
2140
2152
}
2141
2153
2142
2154
// All basic checks have passed, charge baseline instructions.
2155
+ let old_memory_usage = canister. memory_usage ( ) ;
2143
2156
let mut canister_clone = canister. clone ( ) ;
2144
2157
2145
2158
if let Err ( err) = self . cycles_account_manager . consume_cycles_for_instructions (
@@ -2224,6 +2237,25 @@ impl CanisterManager {
2224
2237
) ;
2225
2238
}
2226
2239
2240
+ if let Err ( err) = self . memory_usage_checks (
2241
+ subnet_size,
2242
+ canister,
2243
+ round_limits,
2244
+ new_memory_usage,
2245
+ old_memory_usage,
2246
+ resource_saturation,
2247
+ ) {
2248
+ return ( Err ( err) , instructions_used) ;
2249
+ }
2250
+
2251
+ // Actually deduct memory from the subnet. It's safe to unwrap
2252
+ // here because we already checked the available memory above.
2253
+ round_limits. subnet_available_memory . try_decrement (
2254
+ new_memory_usage. saturating_sub ( & old_memory_usage) ,
2255
+ NumBytes :: from ( 0 ) ,
2256
+ NumBytes :: from ( 0 ) ,
2257
+ ) . expect ( "Error: Cannot fail to decrement SubnetAvailableMemory after checking for availability" ) ;
2258
+
2227
2259
// Charge for loading the snapshot of the canister.
2228
2260
if let Err ( err) = self . cycles_account_manager . consume_cycles_for_instructions (
2229
2261
& sender,
0 commit comments