FeatureGate overview

    When using MOSN, you may want to enable some features at startup and disable those that you do not need. To meet this requirement, MOSN provides a feature switch: FeatureGate.

    FeatureGate specifies a pair of states (enabled/disabled) for features in MOSN. Each feature has its own default state. Different MOSN versions support different features and default states. FeatureGate describes features and their states in a string in the form of . Multiple features and states are separated by a comma.

    FeatureGate supports not only feature switching, but also extensible development. During MOSN-based secondary development, you can use the following FeatureGate capabilities:

    • Feature switching. You can use FeatureGate to enable/disable a specific feature.
    • Feature dependency management. This includes startup sequence and enabled/disabled state dependencies between features.
      • For example, we have two features A and B implemented in MOSN. Initialization of Feature B depends on the initialization result of Feature A. That is to say Feature B depends on Feature A. When Feature A is disabled, Feature B is disabled or downgraded accordingly. FeatureGate provides a simple mechanism to help you focus on feature development, without being distracted by the management of startup items and dependencies.

    FeatureGate is mainly intended for secondary development of different features in MOSN.

    FeatureGate-based development

    1. // Returns the current status of a feature, where "true" indicates "enabled", and "false" indicates "disabled".
    2. func Enabled(key Feature) bool
    3. // Subscribes to a feature and returns its status after subscription finishes.
    4. // Returns whether the subscribed feature is enabled after it is initialized.
    5. // Returns "false" if the subscribed feature is disabled; returns a subscription timeout error if
    6. // the initialization is still not finished after the subscription times out. If the timeout value is less than or equal to 0, no subscription timeout error is returned.
    7. func Subscribe(key Feature, timeout time.Duration) (bool, error)
    8. // Sets the state of FeatureGate. "value" is a complete FeatureGate description.
    9. func Set(value string) error
    10. // Sets the state of FeatureGate. "key" of "map" is the key of the feature, and "value" is the expected state of the feature.
    11. func SetFromMap(m map[string]bool) error
    12. // Registers a new feature with FeatureGate.
    13. func AddFeatureSpec(key Feature, spec FeatureSpec) error
    14. // Sets the state of a feature.
    15. func SetFeatureState(key Feature, enable bool) error
    16. // Enables feature initialization.
    17. func StartInit()
    18. // Waits for the initialization of all features to finish.
    19. func WaitInitFinsh() error

    StartInit and WaitInitFinsh are called by MOSN and can be ignored during secondary development based on MOSN. Generally, Set and SetFromMap can also be ignored. All the above APIs are exposed by the default non-exportable global FeatureGate object. You can use the default FeatureGate object without creating a new one, unless otherwise required in special cases (for example, when writing unit tests).

    The following example shows how FeatureGate is implemented.

    1. type knownFeatureSpec struct {
    2. FeatureSpec
    3. once sync.Once
    4. channel chan struct{}
    5. }
    6. type Feature string
    7. type FeatureGate struct {
    8. // lock guards writes to known, enabled, and reads/writes of closed
    9. lock sync.Mutex
    10. // inited is set to true when StartInit is called.
    11. inited bool
    12. wg sync.WaitGroup
    13. once sync.Once
    14. }

    FeatureGate consists of the following member variables:

    • map: records all supported features;
    • inited: a status indicator that indicates whether FeatureGate has been initialized;
    • once: used to ensure that FeatureGate is initialized only once;
    • WaitGroup: used to synchronize the feature initialization result;
    • Mutex: used for concurrency control.

    According to the design of FeatureGate, you can add new features by calling the method, and modify their states by calling the Set method. However, initialization of all features is uniformly implemented by executing the Init function. After Init is executed, no feature can be added, and no feature state can be modified. That is why we need the inited status indicator to record the initialization action. knownFeatureSpec is a non-exportable structure that specifies FeatureSpec of different features. Both once and channel are used for subscription and initialization in FeatureGate, and are not detailed here. The following describes the definition of FeatureSpec, which is the core data structure for FeatureGate-based development.

    • prerelease refers to a non-exportable type. There are only three conventional exportable variables whose type is prerelease. It is equivalent to the Enum type in conventional languages and is used to describe feature information. The role of prerelease is currently not clear.
    • FeatureSpec can be implemented with your own code. Generally, you can use BaseFeatureSpec provided by the framework or implement a new one based on BaseFeatureSpec. As described in the following comments, you generally only need to encapsulate an additional InitFunc function to implement this API.
    1. // BaseFeatureSpec is a basic implementation of FeatureSpec.
    2. // Usually, a feature spec just need an init func.
    3. type BaseFeatureSpec struct {
    4. // Default state.
    5. DefaultValue bool
    6. // Specifies whether the state can be modified, where "true" indicates that the feature can only remain in the default state.
    7. // Generally, when it is set to "true" here, the default state is also "true".
    8. // This feature is mainly used as a "base dependency" for other features.
    9. IsLockedDefault bool
    10. PreReleaseValue prerelease
    11. stateValue bool // stateValue shoule be setted by SetState
    12. inited int32 // inited cannot be setted
    13. }

    1. Basic global switch

    In feature switching, the most basic usage scenario of FeatureGate, FeatureGate works like a global variable. if conditional judgement is used to execute different logic here. This allows FeatureGate to integrate all feature switching control parameters into the startup parameters.

    1. var featureName featuregate.Feature = "simple_feature"
    2. func init() {
    3. fs := &featuregate.BaseFeatureSpec{
    4. DefaultValue: true
    5. }
    6. featuregate.AddFeatureSpec(featureName,fs)
    7. }
    8. func myfunc() {
    9. if featuregate.Enable(featureName) {
    10. dosth()
    11. dosth2()
    12. }
    13. }

    2. Initialization operation

    The InitFunc function enables related initialization procedures to be uniformly performed when MOSN starts. If a feature is disabled, it will not be executed by InitFunc.

    3. Feature dependency management

    This is the most important capability of FeatureGate. It easily solves the following problem.

    • B and C depend on A, which means we must enable A before we can enable B and C. D depends on B, which means we must enable B before we can enable D.
    • If A is not enabled, B cannot be enabled, but C can still work in downgrade mode.
    • The four features can be implemented separately in FeatureGate as follows.
    1. var FeatureA featuregate.Feature = "A"
    2. func init() {
    3. fs := &featuregate.BaseFeatureSpec{
    4. DefaultValue: true
    5. }
    6. featuregate.AddFeatureSpec(FeatureA,fs)
    7. }
    1. var FeatureB featuregate.Feature = "B"
    2. type FB struct {
    3. *BaseFeatureSpec
    4. }
    5. func (f *FB) InitFunc() {
    6. enabled, err := featuregate.Subscribe(FeatureA, 5 * time.Second)
    7. if err != nil || !enabled {
    8. f.SetState(false) // If feature A is not enabled, feature B will not be enabled.
    9. }
    10. }
    1. type FeatureD featuregate.Feature = "D"
    2. type FD struct {
    3. *BaseFeatureSpec
    4. }
    5. func (f *FD) InitFunc() {
    6. enabled, err := featuregate.Subscribe(FeatureB, 0) // No timeout. Waits until B is enabled.
    7. if err != nil || !enabled {
    8. return
    9. }
    10. f.Start()
    11. }
    12. func (f *FD) Start() {
    13. }

    Why is FeatureGate used instead of configuration files?

    • Unlike configuration files that must be parsed first, FeatureGate is more efficient in implementing advanced features.