1 package interfaces 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 // DataSourceStatusProvider is an interface for querying the status of a DataSource. The data source is the 9 // component that receives updates to feature flag data; normally this is a streaming connection, but it 10 // could be polling or file data depending on your configuration. 11 // 12 // An implementation of this interface is returned by 13 // [github.com/launchdarkly/go-server-sdk/v6.LDClient.GetDataSourceStatusProvider()]. 14 // Application code should not implement this interface. 15 // 16 // There are three ways to interact with the data source status. One is to simply get the current status; 17 // if its State property is DataSourceStateValid, then the SDK is able to receive feature flag updates. 18 // 19 // status := client.GetDataSourceStatusProvider().GetStatus() 20 // isValid = status.State == interfaces.DataSourceStateValid 21 // 22 // Second, you can use AddStatusListener to get a channel that provides a status update whenever the 23 // connection has an error or starts working again. 24 // 25 // statusCh := client.GetDataSourceStatusProvider().AddStatusListener() 26 // go func() { 27 // for newStatus := range statusCh { 28 // log.Printf("data source status is now: %+v", newStatus) 29 // } 30 // }() 31 // 32 // Third, you can use WaitFor to block until the data source has the desired status. For instance, if you 33 // did not want to wait for a connection when you originally created the client, you could set the 34 // timeout to zero so that the connection happens in the background. Then, when you need to do something 35 // that requires a valid connection (possibly on another goroutine), you can wait until it is valid. 36 // 37 // client, _ := ld.MakeCustomClient(sdkKey, config, 0) 38 // 39 // // later... 40 // inited := client.GetDataSourceStatusProvider().WaitFor(interfaces.DataSourceStateValid, 10 * time.Second) 41 // if !inited { 42 // // do whatever is appropriate if initialization has timed out 43 // } 44 type DataSourceStatusProvider interface { 45 // GetStatus returns the current status of the data source. 46 // 47 // All of the built-in data source implementations are guaranteed to update this status whenever they 48 // successfully initialize, encounter an error, or recover after an error. 49 GetStatus() DataSourceStatus 50 51 // AddStatusListener subscribes for notifications of status changes. The returned channel will receive a 52 // new DataSourceStatus value for any change in status. 53 // 54 // The listener will be notified whenever any property of the status has changed. See DataSourceStatus for 55 // an explanation of the meaning of each property and what could cause it to change. 56 // 57 // It is the caller's responsibility to consume values from the channel. Allowing values to accumulate in 58 // the channel can cause an SDK goroutine to be blocked. If you no longer need the channel, call 59 // RemoveStatusListener. 60 AddStatusListener() <-chan DataSourceStatus 61 62 // RemoveStatusListener unsubscribes from notifications of status changes. The specified channel must be 63 // one that was previously returned by AddStatusListener(); otherwise, the method has no effect. 64 RemoveStatusListener(listener <-chan DataSourceStatus) 65 66 // WaitFor is a synchronous method for waiting for a desired connection state. 67 // 68 // If the current state is already desiredState when this method is called, it immediately returns. 69 // Otherwise, it blocks until 1. the state has become desiredState, 2. the state has become 70 // DataSourceStateOff (since that is a permanent condition), or 3. the specified timeout elapses. 71 // 72 // A scenario in which this might be useful is if you want to create the LDClient without waiting 73 // for it to initialize, and then wait for initialization at a later time or on a different goroutine: 74 // 75 // // create the client but do not wait 76 // client = ld.MakeCustomClient(sdkKey, config, 0) 77 // 78 // // later, possibly on another goroutine: 79 // inited := client.GetDataSourceStatusProvider().WaitFor(DataSourceStateValid, 10 * time.Second) 80 // if !inited { 81 // // do whatever is appropriate if initialization has timed out 82 // } 83 WaitFor(desiredState DataSourceState, timeout time.Duration) bool 84 } 85 86 // DataSourceStatus is information about the data source's status and the last status change. 87 // 88 // See [DataSourceStatusProvider]. 89 type DataSourceStatus struct { 90 // State represents the overall current state of the data source. It will always be one of the 91 // DataSourceState constants such as DataSourceStateValid. 92 State DataSourceState 93 94 // StateSince is the date/time that the value of State most recently changed. 95 // 96 // The meaning of this depends on the current State: 97 // - For DataSourceStateInitializing, it is the time that the SDK started initializing. 98 // - For DataSourceStateValid, it is the time that the data source most recently entered a valid 99 // state, after previously having been either Initializing or Interrupted. 100 // - For DataSourceStateInterrupted, it is the time that the data source most recently entered an 101 // error state, after previously having been Valid. 102 // - For DataSourceStateOff, it is the time that the data source encountered an unrecoverable error 103 // or that the SDK was explicitly shut down. 104 StateSince time.Time 105 106 // LastError is information about the last error that the data source encountered, if any. 107 // 108 // This property should be updated whenever the data source encounters a problem, even if it does 109 // not cause State to change. For instance, if a stream connection fails and the 110 // state changes to DataSourceStateInterrupted, and then subsequent attempts to restart the 111 // connection also fail, the state will remain Interrupted but the error information 112 // will be updated each time-- and the last error will still be reported in this property even if 113 // the state later becomes Valid. 114 // 115 // If no error has ever occurred, this field will be an empty DataSourceErrorInfo{}. 116 LastError DataSourceErrorInfo 117 } 118 119 // String returns a simple string representation of the status. 120 func (e DataSourceStatus) String() string { 121 return fmt.Sprintf("Status(%s,%s,%s)", e.State, e.StateSince.Format(time.RFC3339), e.LastError) 122 } 123 124 // DataSourceState is any of the allowable values for [DataSourceStatus].State. 125 // 126 // See [DataSourceStatusProvider]. 127 type DataSourceState string 128 129 const ( 130 // DataSourceStateInitializing is the initial state of the data source when the SDK is being 131 // initialized. 132 // 133 // If it encounters an error that requires it to retry initialization, the state will remain at 134 // Initializing until it either succeeds and becomes DataSourceStateValid, or permanently fails and 135 // becomes DataSourceStateOff. 136 DataSourceStateInitializing DataSourceState = "INITIALIZING" 137 138 // DataSourceStateValid indicates that the data source is currently operational and has not had 139 // any problems since the last time it received data. 140 // 141 // In streaming mode, this means that there is currently an open stream connection and that at least 142 // one initial message has been received on the stream. In polling mode, it means that the last poll 143 // request succeeded. 144 DataSourceStateValid DataSourceState = "VALID" 145 146 // DataSourceStateInterrupted indicates that the data source encountered an error that it will 147 // attempt to recover from. 148 // 149 // In streaming mode, this means that the stream connection failed, or had to be dropped due to some 150 // other error, and will be retried after a backoff delay. In polling mode, it means that the last poll 151 // request failed, and a new poll request will be made after the configured polling interval. 152 DataSourceStateInterrupted DataSourceState = "INTERRUPTED" 153 154 // DataSourceStateOff indicates that the data source has been permanently shut down. 155 // 156 // This could be because it encountered an unrecoverable error (for instance, the LaunchDarkly service 157 // rejected the SDK key; an invalid SDK key will never become valid), or because the SDK client was 158 // explicitly shut down. 159 DataSourceStateOff DataSourceState = "OFF" 160 ) 161 162 // DataSourceErrorInfo is a description of an error condition that the data source encountered. 163 // 164 // See [DataSourceStatusProvider]. 165 type DataSourceErrorInfo struct { 166 // Kind is the general category of the error. It will always be one of the DataSourceErrorKind 167 // constants such as DataSourceErrorKindNetworkError, or "" if there have not been any errors. 168 Kind DataSourceErrorKind 169 170 // StatusCode is the HTTP status code if the error was DataSourceErrorKindErrorResponse, or zero 171 // otherwise. 172 StatusCode int 173 174 // Message is any any additional human-readable information relevant to the error. The format of 175 // this message is subject to change and should not be relied on programmatically. 176 Message string 177 178 // Time is the date/time that the error occurred. 179 Time time.Time 180 } 181 182 // String returns a simple string representation of the error. 183 func (e DataSourceErrorInfo) String() string { 184 ret := string(e.Kind) 185 if e.StatusCode > 0 || e.Message != "" { 186 ret += "(" 187 if e.StatusCode > 0 { 188 ret += fmt.Sprintf("%d", e.StatusCode) 189 } 190 if e.Message != "" { 191 if e.StatusCode > 0 { 192 ret += "," 193 } 194 ret += e.Message 195 } 196 ret += ")" 197 } 198 if !e.Time.IsZero() { 199 ret += fmt.Sprintf("@%s", e.Time.Format(time.RFC3339)) 200 } 201 return ret 202 } 203 204 // DataSourceErrorKind is any of the allowable values for [DataSourceErrorInfo].Kind. 205 // 206 // See [DataSourceStatusProvider]. 207 type DataSourceErrorKind string 208 209 const ( 210 // DataSourceErrorKindUnknown indicates an unexpected error, such as an uncaught exception, 211 // further described by DataSourceErrorInfo.Message. 212 DataSourceErrorKindUnknown DataSourceErrorKind = "UNKNOWN" 213 214 // DataSourceErrorKindNetworkError represents an I/O error such as a dropped connection. 215 DataSourceErrorKindNetworkError DataSourceErrorKind = "NETWORK_ERROR" 216 217 // DataSourceErrorKindErrorResponse means the LaunchDarkly service returned an HTTP response 218 // with an error status, available in DataSourceErrorInfo.StatusCode. 219 DataSourceErrorKindErrorResponse DataSourceErrorKind = "ERROR_RESPONSE" 220 221 // DataSourceErrorKindInvalidData means the SDK received malformed data from the LaunchDarkly 222 // service. 223 DataSourceErrorKindInvalidData DataSourceErrorKind = "INVALID_DATA" 224 225 // DataSourceErrorKindStoreError means the data source itself is working, but when it tried 226 // to put an update into the data store, the data store failed (so the SDK may not have the 227 // latest data). 228 // 229 // Data source implementations do not need to report this kind of error; it will be 230 // automatically reported by the SDK whenever one of the update methods of DataSourceUpdateSink 231 // encounters a failure. 232 DataSourceErrorKindStoreError DataSourceErrorKind = "STORE_ERROR" 233 ) 234