crates/libs/core/src/runtime/stateful.rs (92 lines of code) (raw):

// ------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License (MIT). See License.txt in the repo root for license information. // ------------------------------------------------------------ // stateful contains rs definition of stateful traits that user needs to implement use crate::sync::CancellationToken; use crate::types::ReplicaRole; use crate::types::{Epoch, OpenMode, ReplicaInformation, ReplicaSetConfig, ReplicaSetQuorumMode}; use super::stateful_proxy::StatefulServicePartition; /// Represents a stateful service factory that is responsible for creating replicas /// of a specific type of stateful service. Stateful service factories are registered with /// the FabricRuntime by service hosts via register_stateful_service_factory(). pub trait StatefulServiceFactory { /// Called by Service Fabric to create a stateful service replica for a particular service. fn create_replica( &self, servicetypename: &crate::WString, servicename: &crate::WString, initializationdata: &[u8], partitionid: &crate::GUID, replicaid: i64, ) -> crate::Result<impl StatefulServiceReplica>; } /// Defines behavior that governs the lifecycle of a replica, such as startup, initialization, role changes, and shutdown. /// Remarks: /// Stateful service types must implement this interface. The logic of a stateful service type includes behavior that is /// invoked on primary replicas and behavior that is invoked on secondary replicas. #[trait_variant::make(StatefulServiceReplica: Send)] pub trait LocalStatefulServiceReplica: Send + Sync + 'static { /// Opens an initialized service replica so that additional actions can be taken. /// Returns PrimaryReplicator that is used by the stateful service. /// Note: /// Most user calls IFabricStatefulServicePartition.CreateReplicator instead of /// writing their own replicator (TODO: not supported in mssf yet), /// or use FabricCreateKeyValueStoreReplica. async fn open( &self, openmode: OpenMode, partition: &StatefulServicePartition, cancellation_token: CancellationToken, ) -> crate::Result<impl PrimaryReplicator>; /// Changes the role of the service replica to one of the ReplicaRole. /// Returns the service’s new connection address that is to be associated with the replica via Service Fabric Naming. /// Remarks: /// The new role is indicated as a parameter. When the service transitions to the new role, /// the service has a chance to update its current listening address. The listening address is the address /// where clients connect to it and the one returned via the ResolveAsync API. This enables the service when /// it is a primary replica to only claim some resources such as ports when communication from clients is expected. async fn change_role( &self, newrole: ReplicaRole, cancellation_token: CancellationToken, ) -> crate::Result<crate::WString>; /// Closes the service replica gracefully when it is being shut down. async fn close(&self, cancellation_token: CancellationToken) -> crate::Result<()>; /// Ungracefully terminates the service replica. /// Remarks: Network issues resulting in Service Fabric process shutdown /// and the use of ReportFault(FaultType) to report a Permanent fault are examples of ungraceful termination. /// When this method is invoked, the service replica should immediately release and clean up all references and return. fn abort(&self); } /// TODO: replicator has no public documentation #[trait_variant::make(Replicator: Send)] pub trait LocalReplicator: Send + Sync + 'static { /// Opens replicator, and returns the replicator address that is visible to primary /// in ReplicaInformation. /// Remarks: /// Replicator does not have an assigned role yet and should setup listening endpoint. async fn open(&self, cancellation_token: CancellationToken) -> crate::Result<crate::WString>; async fn close(&self, cancellation_token: CancellationToken) -> crate::Result<()>; /// Change the replicator role. /// /// Remarks: /// Replicator change_role is called before Replica change_role. async fn change_role( &self, epoch: &Epoch, role: &ReplicaRole, cancellation_token: CancellationToken, ) -> crate::Result<()>; /// (TODO: This doc is from IStateProvider but not Replicator.) /// Indicates to a replica that the configuration of a replica set has changed due to /// a change or attempted change to the primary replica. The change occurs due to failure /// or load balancing of the previous primary replica. Epoch changes act as a barrier by /// segmenting operations into the exact configuration periods in which they were sent /// by a specific primary replica. /// /// Called only on active/idle secondary replicas. Primary replica gets new epoch via change_role call. async fn update_epoch( &self, epoch: &Epoch, cancellation_token: CancellationToken, ) -> crate::Result<()>; /// Get the current LSN, end of log, called on secondaries. /// SF uses this to do primary selection. It is also passed to update_catch_up_replica_set_configuration() /// on primary. Primary uses this for catchup. fn get_current_progress(&self) -> crate::Result<i64>; /// Get the first LSN, beginning of log. /// Remarks: /// SF uses this to determine if other replicas can catch up from this replica. /// Other replica's end of log must be higher than this replica's beginning of log /// in order for the other replica to catchup, otherwise SF needs to drop the other /// replica (if the current replica is chosen to be primary). fn get_catch_up_capability(&self) -> crate::Result<i64>; fn abort(&self); } // Remarks: // Adding a secondary into the partition involves the following steps: // * SF brings up an idle secondary replica S which can be empty or contain // (partial) previous data. // * build_replica is called on primary to copy data into the S. // * S is changed to active secondary // * update_catch_up_replica_set_configuration to include S in the current configuration, // and wait_for_catch_up_quorum are called on primary for final synchronization before SF // grants ReadStatus to S. // * SF grants ReadStatus to S, and replica build completes. // // For primary failover, all active or idle secondaries gets update_epoch() call, and new // primary gets the new epoch from change_role() call. Secondary should fence/reject // operations from the old primary with an older epoch. /// TODO: primary replicator has no public documentation, this is gathered unofficially and /// is subject to change/correction. /// IFabricPrimaryReplicator com interface wrapper. #[trait_variant::make(PrimaryReplicator: Send)] pub trait LocalPrimaryReplicator: Replicator { // SF calls this to indicate that possible data loss has occurred (write quorum loss), // returns is isStateChanged. If true, SF will re-create other secondaries. // The default SF impl might be a pass through to the state provider. async fn on_data_loss(&self, cancellation_token: CancellationToken) -> crate::Result<u8>; // Remarks on replicator configuration: // At any time the replicator can have one or two configurations. There is always a current // configuration which represents the set of replicas that are participating in replication // along with the current write quorum. In addition there can be a previous configuration // which represents the set of replicas that were in the previous configuration. // When there is both a current and previous configuration the replicator must ensure that // writes are acknowledeged by a write quroum of both configurations. /// Informs the replicator there there is a current configuration and a previous configuration. /// Called on primary to inform the set of active secondary replicas that may /// begin to catchup. Idle secondary replicas are not included here. /// /// The total number of replica marked with must_catchup will not exceed the write quorum. /// Secondary to be promoted to new primary is guaranteed to have must_catchup set, /// i.e. it must catch up (have all the data) to be promoted to new primary. /// /// ReplicaInformation: /// current_progress -> The LSN of the replica. -1 if the replicator is already aware of the replica /// (it is in configuration or has been built) otherwise it will be the progress of the remote replica. /// catch_up_capability -> The first LSN of the replica. Similar to current_progress. /// must_catchup -> Set to true only for one replica in the current configuration. fn update_catch_up_replica_set_configuration( &self, currentconfiguration: &ReplicaSetConfig, previousconfiguration: &ReplicaSetConfig, ) -> crate::Result<()>; /// Informs the replicator about the current replica set configuration, and there /// is no longer a previous configuration. /// Remarks: /// Replicas here are not marked as must_catchup. fn update_current_replica_set_configuration( &self, currentconfiguration: &ReplicaSetConfig, ) -> crate::Result<()>; /// Called on primary to wait for replicas to catch up, before /// accepting writes. /// /// mssf-core enables IFabricReplicatorCatchupSpecificQuorum for replicators, /// so ReplicaSetQuarumMode::Write can be used. /// /// catchupmode: /// All -> full quorum. All replicas needs to catch up. /// Write -> write quorum, for replicas specified in update_catch_up_replica_set_configuration(currentconfiguration...), /// a subset of replicas that can form a write quorum must catchup, and the subset must include /// the replica with must_catchup set to true (primary candidate). /// This is used only in primary swap case in SF, to avoid slow replica preventing/slowing down the swap. /// Remarks: /// Catchup (or quorum catchup) in SF means that the lowest LSN among all replicas (or quorum of replicas /// including the must catchup replica) in the current configuration is equal or greater than /// the current committed LSN. /// /// For swap primary case, double catchup feature is enabled by default. /// SF can first call this api before initiating write status revokation. SF then revoke write status, /// and call this again. This allows replicator to catch up with write status granted to make necessary writes for /// catch up. There is a chance that replicator takes forever to complete this api with mode ReplicaSetQuarumMode::All /// since client/user can keep writing and advancing the committed LSN, but for it most likely would not /// stall in mode ReplicaSetQuarumMode::Write. /// In other cases when client write is not impacted (like secondary restart), /// SF may call this api only once with write status granted. /// /// Implementor should not assume when this is called in relation to other api calls, /// but instead follow the semantics of what catchup should do. async fn wait_for_catch_up_quorum( &self, catchupmode: ReplicaSetQuorumMode, cancellation_token: CancellationToken, ) -> crate::Result<()>; /// Transferring state up to the current quorum LSN to a new or existing replica /// that is outside the current configuration. (not included in update_catch_up_replica_set_configuration) /// /// replica: /// role is IdleSecondary /// status set to up or down /// current progress is -1 /// catchup capability is -1 /// must catchup is false /// /// remarks: /// SF can cancel the replica build operation by calling the cancellation token. /// Replica being built or completed built does not count towards quorum and is /// not part of the current configuration. Replica cannot be in build and be in the /// configuration at the same time. Idle replica it maybe added by SF to the configuration /// by calling update_x_configuration(). async fn build_replica( &self, replica: &ReplicaInformation, cancellation_token: CancellationToken, ) -> crate::Result<()>; /// Notifies primary that an idle replica built by build_replica() api call /// has gone down and replicator should not send more operations to that replica /// and should release all resources. /// Remarks: /// Removing replicas already in the partition, update_catch_up_replica_set_configuration /// is called instead with ReplicaSetConfig not containng the to be removed replica. /// SF does not call remove_replica on the replica where build_replica is still running. fn remove_replica(&self, replicaid: i64) -> crate::Result<()>; }