mssf_core/runtime/stateful_traits.rs
1// ------------------------------------------------------------
2// Copyright (c) Microsoft Corporation. All rights reserved.
3// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
4// ------------------------------------------------------------
5
6// stateful contains rs definition of stateful traits that user needs to implement
7
8use crate::runtime::executor::BoxedCancelToken;
9use crate::types::ReplicaRole;
10
11use crate::types::{Epoch, OpenMode, ReplicaInformation, ReplicaSetConfig, ReplicaSetQuorumMode};
12
13/// Represents a stateful service factory that is responsible for creating replicas
14/// of a specific type of stateful service. Stateful service factories are registered with
15/// the FabricRuntime by service hosts via register_stateful_service_factory().
16pub trait IStatefulServiceFactory: Send + Sync + 'static {
17 /// Called by Service Fabric to create a stateful service replica for a particular service.
18 fn create_replica(
19 &self,
20 servicetypename: crate::WString,
21 servicename: crate::types::Uri,
22 initializationdata: &[u8],
23 partitionid: crate::GUID,
24 replicaid: i64,
25 ) -> crate::Result<Box<dyn IStatefulServiceReplica>>;
26}
27
28/// Defines behavior that governs the lifecycle of a replica, such as startup, initialization, role changes, and shutdown.
29/// Remarks:
30/// Stateful service types must implement this interface. The logic of a stateful service type includes behavior that is
31/// invoked on primary replicas and behavior that is invoked on secondary replicas.
32#[async_trait::async_trait]
33pub trait IStatefulServiceReplica: Send + Sync + 'static {
34 /// Opens an initialized service replica so that additional actions can be taken.
35 /// Returns PrimaryReplicator that is used by the stateful service.
36 /// Note:
37 /// Most user calls IFabricStatefulServicePartition.CreateReplicator instead of
38 /// writing their own replicator (TODO: not supported in mssf yet),
39 /// or use FabricCreateKeyValueStoreReplica.
40 async fn open(
41 &self,
42 openmode: OpenMode,
43 partition: std::sync::Arc<dyn IStatefulServicePartition>,
44 cancellation_token: BoxedCancelToken,
45 ) -> crate::Result<Box<dyn IPrimaryReplicator>>;
46
47 /// Changes the role of the service replica to one of the ReplicaRole.
48 /// Returns the service’s new connection address that is to be associated with the replica via Service Fabric Naming.
49 /// Remarks:
50 /// The new role is indicated as a parameter. When the service transitions to the new role,
51 /// the service has a chance to update its current listening address. The listening address is the address
52 /// where clients connect to it and the one returned via the ResolveAsync API. This enables the service when
53 /// it is a primary replica to only claim some resources such as ports when communication from clients is expected.
54 async fn change_role(
55 &self,
56 newrole: ReplicaRole,
57 cancellation_token: BoxedCancelToken,
58 ) -> crate::Result<crate::WString>;
59
60 /// Closes the service replica gracefully when it is being shut down.
61 async fn close(&self, cancellation_token: BoxedCancelToken) -> crate::Result<()>;
62
63 /// Ungracefully terminates the service replica.
64 /// Remarks: Network issues resulting in Service Fabric process shutdown
65 /// and the use of ReportFault(FaultType) to report a Permanent fault are examples of ungraceful termination.
66 /// When this method is invoked, the service replica should immediately release and clean up all references and return.
67 fn abort(&self);
68}
69
70/// TODO: replicator has no public documentation
71#[async_trait::async_trait]
72pub trait IReplicator: Send + Sync + 'static {
73 /// Opens replicator, and returns the replicator address that is visible to primary
74 /// in ReplicaInformation.
75 /// Remarks:
76 /// Replicator does not have an assigned role yet and should setup listening endpoint.
77 async fn open(&self, cancellation_token: BoxedCancelToken) -> crate::Result<crate::WString>;
78 async fn close(&self, cancellation_token: BoxedCancelToken) -> crate::Result<()>;
79
80 /// Change the replicator role.
81 ///
82 /// Remarks:
83 /// Replicator change_role is called before Replica change_role.
84 async fn change_role(
85 &self,
86 epoch: Epoch,
87 role: ReplicaRole,
88 cancellation_token: BoxedCancelToken,
89 ) -> crate::Result<()>;
90
91 /// (TODO: This doc is from IStateProvider but not Replicator.)
92 /// Indicates to a replica that the configuration of a replica set has changed due to
93 /// a change or attempted change to the primary replica. The change occurs due to failure
94 /// or load balancing of the previous primary replica. Epoch changes act as a barrier by
95 /// segmenting operations into the exact configuration periods in which they were sent
96 /// by a specific primary replica.
97 ///
98 /// Called only on active/idle secondary replicas. Primary replica gets new epoch via change_role call.
99 async fn update_epoch(
100 &self,
101 epoch: Epoch,
102 cancellation_token: BoxedCancelToken,
103 ) -> crate::Result<()>;
104
105 /// Get the current LSN, end of log, called on secondaries.
106 /// SF uses this to do primary selection. It is also passed to update_catch_up_replica_set_configuration()
107 /// on primary. Primary uses this for catchup.
108 fn get_current_progress(&self) -> crate::Result<i64>;
109
110 /// Get the first LSN, beginning of log.
111 /// Remarks:
112 /// SF uses this to determine if other replicas can catch up from this replica.
113 /// Other replica's end of log must be higher than this replica's beginning of log
114 /// in order for the other replica to catchup, otherwise SF needs to drop the other
115 /// replica (if the current replica is chosen to be primary).
116 fn get_catch_up_capability(&self) -> crate::Result<i64>;
117 fn abort(&self);
118}
119
120// Remarks:
121// Adding a secondary into the partition involves the following steps:
122// * SF brings up an idle secondary replica S which can be empty or contain
123// (partial) previous data.
124// * build_replica is called on primary to copy data into the S.
125// * S is changed to active secondary
126// * update_catch_up_replica_set_configuration to include S in the current configuration,
127// and wait_for_catch_up_quorum are called on primary for final synchronization before SF
128// grants ReadStatus to S.
129// * SF grants ReadStatus to S, and replica build completes.
130//
131// For primary failover, all active or idle secondaries gets update_epoch() call, and new
132// primary gets the new epoch from change_role() call. Secondary should fence/reject
133// operations from the old primary with an older epoch.
134
135/// TODO: primary replicator has no public documentation, this is gathered unofficially and
136/// is subject to change/correction.
137/// IFabricPrimaryReplicator com interface wrapper.
138#[async_trait::async_trait]
139pub trait IPrimaryReplicator: IReplicator {
140 // SF calls this to indicate that possible data loss has occurred (write quorum loss),
141 // returns is isStateChanged. If true, SF will re-create other secondaries.
142 // The default SF impl might be a pass through to the state provider.
143 async fn on_data_loss(&self, cancellation_token: BoxedCancelToken) -> crate::Result<u8>;
144
145 // Remarks on replicator configuration:
146 // At any time the replicator can have one or two configurations. There is always a current
147 // configuration which represents the set of replicas that are participating in replication
148 // along with the current write quorum. In addition there can be a previous configuration
149 // which represents the set of replicas that were in the previous configuration.
150 // When there is both a current and previous configuration the replicator must ensure that
151 // writes are acknowledeged by a write quroum of both configurations.
152
153 /// Informs the replicator there there is a current configuration and a previous configuration.
154 /// Called on primary to inform the set of active secondary replicas that may
155 /// begin to catchup. Idle secondary replicas are not included here.
156 ///
157 /// The total number of replica marked with must_catchup will not exceed the write quorum.
158 /// Secondary to be promoted to new primary is guaranteed to have must_catchup set,
159 /// i.e. it must catch up (have all the data) to be promoted to new primary.
160 ///
161 /// ReplicaInformation:
162 /// current_progress -> The LSN of the replica. -1 if the replicator is already aware of the replica
163 /// (it is in configuration or has been built) otherwise it will be the progress of the remote replica.
164 /// catch_up_capability -> The first LSN of the replica. Similar to current_progress.
165 /// must_catchup -> Set to true only for one replica in the current configuration.
166 fn update_catch_up_replica_set_configuration(
167 &self,
168 currentconfiguration: ReplicaSetConfig,
169 previousconfiguration: ReplicaSetConfig,
170 ) -> crate::Result<()>;
171
172 /// Informs the replicator about the current replica set configuration, and there
173 /// is no longer a previous configuration.
174 /// Remarks:
175 /// Replicas here are not marked as must_catchup.
176 fn update_current_replica_set_configuration(
177 &self,
178 currentconfiguration: ReplicaSetConfig,
179 ) -> crate::Result<()>;
180
181 /// Called on primary to wait for replicas to catch up, before
182 /// accepting writes.
183 ///
184 /// mssf-core enables IFabricReplicatorCatchupSpecificQuorum for replicators,
185 /// so ReplicaSetQuarumMode::Write can be used.
186 ///
187 /// catchupmode:
188 /// All -> full quorum. All replicas needs to catch up.
189 /// Write -> write quorum, for replicas specified in update_catch_up_replica_set_configuration(currentconfiguration...),
190 /// a subset of replicas that can form a write quorum must catchup, and the subset must include
191 /// the replica with must_catchup set to true (primary candidate).
192 /// This is used only in primary swap case in SF, to avoid slow replica preventing/slowing down the swap.
193 /// Remarks:
194 /// Catchup (or quorum catchup) in SF means that the lowest LSN among all replicas (or quorum of replicas
195 /// including the must catchup replica) in the current configuration is equal or greater than
196 /// the current committed LSN.
197 ///
198 /// For swap primary case, double catchup feature is enabled by default.
199 /// SF can first call this api before initiating write status revokation. SF then revoke write status,
200 /// and call this again. This allows replicator to catch up with write status granted to make necessary writes for
201 /// catch up. There is a chance that replicator takes forever to complete this api with mode ReplicaSetQuarumMode::All
202 /// since client/user can keep writing and advancing the committed LSN, but for it most likely would not
203 /// stall in mode ReplicaSetQuarumMode::Write.
204 /// In other cases when client write is not impacted (like secondary restart),
205 /// SF may call this api only once with write status granted.
206 ///
207 /// Implementor should not assume when this is called in relation to other api calls,
208 /// but instead follow the semantics of what catchup should do.
209 async fn wait_for_catch_up_quorum(
210 &self,
211 catchupmode: ReplicaSetQuorumMode,
212 cancellation_token: BoxedCancelToken,
213 ) -> crate::Result<()>;
214
215 /// Transferring state up to the current quorum LSN to a new or existing replica
216 /// that is outside the current configuration. (not included in update_catch_up_replica_set_configuration)
217 ///
218 /// replica:
219 /// role is IdleSecondary
220 /// status set to up or down
221 /// current progress is -1
222 /// catchup capability is -1
223 /// must catchup is false
224 ///
225 /// remarks:
226 /// SF can cancel the replica build operation by calling the cancellation token.
227 /// Replica being built or completed built does not count towards quorum and is
228 /// not part of the current configuration. Replica cannot be in build and be in the
229 /// configuration at the same time. Idle replica it maybe added by SF to the configuration
230 /// by calling update_x_configuration().
231 async fn build_replica(
232 &self,
233 replica: ReplicaInformation,
234 cancellation_token: BoxedCancelToken,
235 ) -> crate::Result<()>;
236
237 /// Notifies primary that an idle replica built by build_replica() api call
238 /// has gone down and replicator should not send more operations to that replica
239 /// and should release all resources.
240 /// Remarks:
241 /// Removing replicas already in the partition, update_catch_up_replica_set_configuration
242 /// is called instead with ReplicaSetConfig not containng the to be removed replica.
243 /// SF does not call remove_replica on the replica where build_replica is still running.
244 fn remove_replica(&self, replicaid: i64) -> crate::Result<()>;
245}
246
247impl std::fmt::Debug for dyn IPrimaryReplicator {
248 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249 f.debug_struct("IPrimaryReplicator").finish()
250 }
251}
252
253pub trait IStatefulServicePartition: Send + Sync + 'static {
254 /// Creates a FabricReplicator with the specified settings and returns it to the replica.
255 /// Not supported currently.
256 fn create_replicator(&self) -> crate::Result<Box<dyn IPrimaryReplicator>>;
257
258 /// Provides access to the ServicePartitionInformation of the service, which contains the partition type and ID.
259 fn get_partition_information(&self)
260 -> crate::Result<crate::types::ServicePartitionInformation>;
261
262 /// Used to check the readiness of the replica in regard to read operations.
263 /// The ReadStatus should be checked before the replica is servicing a customer request that is a read operation.
264 fn get_read_status(&self) -> crate::Result<crate::types::ServicePartitionAccessStatus>;
265
266 /// Used to check the readiness of the partition in regard to write operations.
267 /// The WriteStatus should be checked before the replica services a customer request that is a write operation.
268 fn get_write_status(&self) -> crate::Result<crate::types::ServicePartitionAccessStatus>;
269
270 /// Reports load for the current replica in the partition.
271 /// Remarks:
272 /// The reported metrics should correspond to those that are provided in the ServiceLoadMetricDescription
273 /// as a part of the ServiceDescription that is used to create the service. Load metrics that are not
274 /// present in the description are ignored. Reporting custom metrics allows Service Fabric to balance
275 /// services that are based on additional custom information.
276 fn report_load(&self, metrics: &[crate::types::LoadMetric]) -> crate::Result<()>;
277
278 /// Enables the replica to report a fault to the runtime and indicates that it has encountered
279 /// an error from which it cannot recover and must either be restarted or removed.
280 fn report_fault(&self, fault_type: crate::types::FaultType) -> crate::Result<()>;
281
282 /// Reports the move cost for a replica.
283 /// Remarks:
284 /// Services can report move cost of a replica using this method.
285 /// While the Service Fabric Resource Balances searches for the best balance in the cluster,
286 /// it examines both load information and move cost of each replica.
287 /// Resource balances will prefer to move replicas with lower cost in order to achieve balance.
288 fn report_move_cost(&self, move_cost: crate::types::MoveCost) -> crate::Result<()>;
289
290 // Remarks:
291 // The health information describes the report details, like the source ID, the property,
292 // the health state and other relevant details. The partition uses an internal health client
293 // to send the reports to the health store. The client optimizes messages to Health Manager
294 // by batching reports per a configured duration (Default: 30 seconds). If the report has high priority,
295 // you can specify send options to send it immediately.
296
297 /// Reports current partition health.
298 fn report_partition_health(
299 &self,
300 healthinfo: &crate::types::HealthInformation,
301 ) -> crate::Result<()>;
302
303 /// Reports health on the current stateful service replica of the partition.
304 fn report_replica_health(
305 &self,
306 healthinfo: &crate::types::HealthInformation,
307 ) -> crate::Result<()>;
308
309 /// Returns the com object for proxy interop. This is only used when using Proxy.
310 fn try_get_com(
311 &self,
312 ) -> crate::Result<&mssf_com::FabricRuntime::IFabricStatefulServicePartition>;
313}
314
315impl std::fmt::Debug for dyn IStatefulServicePartition {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 f.debug_struct("IStatefulServicePartition").finish()
318 }
319}