@@ -5,6 +5,17 @@ use ic_interfaces::execution_environment::{
5
5
use ic_types:: NumInstructions ;
6
6
use std:: sync:: { Arc , Condvar , Mutex } ;
7
7
8
+ // The upper bound on the number of supported execution slices.
9
+ //
10
+ // It is set to 400 because 500 the number of rounds in an epoch,
11
+ // which the interval between two checkpoints.
12
+ //
13
+ // Note that currently the maximum number of slices:
14
+ // - for update messages is 10.
15
+ // - for upgrade messages is 100.
16
+ // See constants in `rs/config/src/subnet_config.rs`
17
+ const MAX_NUM_SLICES : i64 = 400 ;
18
+
8
19
// Indicates the current state of execution.
9
20
// It start with `Running` and may transition to `Paused`.
10
21
// From `Paused` it transitions either to `Running` or `Aborted`.
@@ -34,19 +45,37 @@ struct State {
34
45
// Invariant: it does not exceed `total_instruction_limit`.
35
46
instructions_executed : i64 ,
36
47
48
+ // The number of remaining execution slices.
49
+ //
50
+ // Note that normally execution finishes when `instructions_executed`
51
+ // exceeds `total_instruction_limit`. This counter is used as an additional
52
+ // safeguard to guarantee fast progress for the case when each slice does
53
+ // not use all its available instructions.
54
+ slices_left : i64 ,
55
+
37
56
// The execution complexity accumulated at the beginning of the round.
38
57
execution_complexity : ExecutionComplexity ,
39
58
}
40
59
41
60
impl State {
42
61
fn new ( total_instruction_limit : i64 , max_slice_instruction_limit : i64 ) -> Self {
43
62
let max_slice_instruction_limit = max_slice_instruction_limit. min ( total_instruction_limit) ;
63
+
64
+ let max_num_slices =
65
+ ( total_instruction_limit / max_slice_instruction_limit. max ( 1 ) ) . min ( MAX_NUM_SLICES ) ;
66
+
67
+ // Since the number of slices is a secondary limit and just a safeguard
68
+ // in addition to the primary limit of instructions, we can give it some
69
+ // slack to ensure that it doesn't interfere in regular cases.
70
+ let max_num_slices_with_slack = ( 2 * max_num_slices) . clamp ( 4 , MAX_NUM_SLICES ) ;
71
+
44
72
let result = Self {
45
73
execution_status : ExecutionStatus :: Running ,
46
74
total_instruction_limit,
47
75
max_slice_instruction_limit,
48
76
slice_instruction_limit : max_slice_instruction_limit,
49
77
instructions_executed : 0 ,
78
+ slices_left : max_num_slices_with_slack,
50
79
execution_complexity : ExecutionComplexity :: default ( ) ,
51
80
} ;
52
81
result. check_invariants ( ) ;
@@ -73,7 +102,7 @@ impl State {
73
102
/// Returns true if the current slice is sufficient to reach the total
74
103
/// instruction limit.
75
104
fn is_last_slice ( & self ) -> bool {
76
- self . slice_instruction_limit >= self . total_instructions_left ( )
105
+ self . slice_instruction_limit >= self . total_instructions_left ( ) || self . slices_left <= 1
77
106
}
78
107
79
108
/// Computes the limit for the next slice taking into account
@@ -83,8 +112,7 @@ impl State {
83
112
let newly_executed = self . newly_executed ( instruction_counter) ;
84
113
let carry_over = ( newly_executed - self . slice_instruction_limit ) . max ( 0 ) ;
85
114
( self . max_slice_instruction_limit - carry_over)
86
- . min ( self . total_instructions_left ( ) )
87
- . max ( 0 )
115
+ . clamp ( 0 , self . total_instructions_left ( ) . max ( 0 ) )
88
116
}
89
117
90
118
/// Returns the number of instructions executed in the current slice.
@@ -113,6 +141,7 @@ impl State {
113
141
. saturating_add ( self . newly_executed ( instruction_counter) ) ;
114
142
self . slice_instruction_limit = self . next_slice_instruction_limit ( instruction_counter) ;
115
143
self . execution_complexity = execution_complexity;
144
+ self . slices_left -= 1 ;
116
145
self . check_invariants ( ) ;
117
146
}
118
147
0 commit comments