/// <summary> /// Starts the HTTP streaming connection to the Couchbase Server and gets the latest configuration for a SASL authenticated Bucket. /// </summary> /// <param name="bucketName">The name of the Couchbase Bucket.</param> /// <param name="password">The SASL password used to connect to the Bucket.</param> /// <returns>A <see cref="IConfigInfo"/> object representing the latest configuration.</returns> public override IConfigInfo GetConfig(string bucketName, string password) { var bucketConfiguration = GetOrCreateConfiguration(bucketName); StartProvider(bucketName, password); var bucketConfig = GetBucketConfig(bucketName, password); try { ConfigLock.EnterWriteLock(); IConfigInfo configInfo = null; var nodes = bucketConfig.Nodes.ToList(); while (nodes.Any()) { try { nodes.Shuffle(); var node = nodes.First(); nodes.Remove(node); IBucketConfig newConfig; var uri = bucketConfig.GetTerseUri(node, bucketConfiguration.UseSsl); using (new SynchronizationContextExclusion()) { using (var httpClient = new CouchbaseHttpClient(bucketName, password)) { var body = httpClient.GetStringAsync(uri).Result; body = body.Replace("$HOST", uri.Host); newConfig = JsonConvert.DeserializeObject<BucketConfig>(body); } } newConfig.Password = password; configInfo = CreateConfigInfo(newConfig); Configs[bucketName] = configInfo; break; } catch (AggregateException e) { Log.Error(e.InnerException); } catch (IOException e) { Log.Error(e); } } if (configInfo == null) { throw new BucketNotFoundException(); } return configInfo; } finally { ConfigLock.ExitWriteLock(); } }
/// <summary> /// Starts the streaming connection to couchbase server that will /// listen for configuration changes and then update the client as needed. /// </summary> /// <remarks> /// Should not be used when a <see cref="SynchronizationContext" /> is present on the thread, as this /// could cause deadlocks. This method is currently only used from within a dedicated thread, /// created by <see cref="HttpStreamingProvider.RegisterObserver"/>, so it is safe because there will not /// be a SynchronizationContext present on the thread. /// </remarks> public void ListenForConfigChanges() { var count = 0; //Make a copy of the nodes and shuffle them for randomness var nodes = _bucketConfig.Nodes.ToList(); using (var httpClient = new CouchbaseHttpClient(_bucketConfig.Name, _bucketConfig.Password)) { httpClient.Timeout = Timeout.InfiniteTimeSpan; //This will keep trying until it runs out of servers to try in the cluster while (nodes.ToList().Any()) { try { //If the main thread has canceled, break out of the loop otherwise //the next node in the server list will be tried; but in this case //we want to shut things down and terminate the thread if (_cancellationToken.IsCancellationRequested) { break; } nodes = nodes.Shuffle(); var node = nodes[0]; nodes.Remove(node); var streamingUri = _bucketConfig.GetTerseStreamingUri(node, _bucketConfig.UseSsl); Log.Info(m => m("Listening to {0}", streamingUri)); var response = httpClient.GetAsync(streamingUri, HttpCompletionOption.ResponseHeadersRead, _cancellationToken) .Result; response.EnsureSuccessStatusCode(); using (var stream = response.Content.ReadAsStreamAsync().Result) { //this will cancel the infinite wait below _cancellationToken.Register(stream.Dispose); stream.ReadTimeout = Timeout.Infinite; using (var reader = new StreamReader(stream, Encoding.UTF8, false)) { string config; while (!_cancellationToken.IsCancellationRequested && ((config = reader.ReadLineAsync().Result) != null)) { if (config != String.Empty) { Log.Info(m => m("configuration changed count: {0}", count++)); Log.Info(m => m("Worker Thread: {0}", Thread.CurrentThread.ManagedThreadId)); var config1 = config; Log.Debug(m => m("{0}", config1)); config = config.Replace("$HOST", streamingUri.Host); var bucketConfig = JsonConvert.DeserializeObject<BucketConfig>(config); bucketConfig.SurrogateHost = GetSurrogateHost(streamingUri); if (_configChangedDelegate != null) { bucketConfig.Password = _bucketConfig.Password; _configChangedDelegate(bucketConfig); } } } } } } catch (AggregateException e) { var exceptions = e.Flatten().InnerExceptions; if (exceptions.OfType<ObjectDisposedException>().Any()) { Log.Info("The config listener has shut down."); } foreach (var ex in exceptions.Where(x => x.GetType() != typeof(ObjectDisposedException))) { Log.Error(ex); } } catch (Exception e) { Log.Error(e); } } } //We tried all nodes in the current configuration, alert the provider that we //need to try to re-bootstrap from the beginning if (nodes.Count == 0) { _errorOccurredDelegate(_bucketConfig); } }