Skip to main content

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}