/StackExchange.Redis/StackExchange/Redis/ConnectionMultiplexer.cs
https://github.com/sgwill/StackExchange.Redis · C# · 1825 lines · 1483 code · 173 blank · 169 comment · 322 complexity · 6eef4a02f5a389a4a140a191f469af5a MD5 · raw file
Large files are truncated click here to view the full file
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.IO;
- using System.IO.Compression;
- using System.Linq;
- using System.Net;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- #if NET40
- using Microsoft.Runtime.CompilerServices;
- #else
- using System.Runtime.CompilerServices;
- #endif
- namespace StackExchange.Redis
- {
- internal static partial class TaskExtensions
- {
- private static readonly Action<Task> observeErrors = ObverveErrors;
- private static void ObverveErrors(this Task task)
- {
- if (task != null) GC.KeepAlive(task.Exception);
- }
- public static Task ObserveErrors(this Task task)
- {
- if (task != null) task.ContinueWith(observeErrors, TaskContinuationOptions.OnlyOnFaulted);
- return task;
- }
- public static Task<T> ObserveErrors<T>(this Task<T> task)
- {
- if (task != null) task.ContinueWith(observeErrors, TaskContinuationOptions.OnlyOnFaulted);
- return task;
- }
- public static ConfiguredTaskAwaitable ForAwait(this Task task)
- {
- return task.ConfigureAwait(false);
- }
- public static ConfiguredTaskAwaitable<T> ForAwait<T>(this Task<T> task)
- {
- return task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Represents an inter-related group of connections to redis servers
- /// </summary>
- public sealed partial class ConnectionMultiplexer : IDisposable
- {
- /// <summary>
- /// Get summary statistics associates with this server
- /// </summary>
- public ServerCounters GetCounters()
- {
- var snapshot = serverSnapshot;
- var counters = new ServerCounters(null);
- for (int i = 0; i < snapshot.Length; i++)
- {
- counters.Add(snapshot[i].GetCounters());
- }
- unprocessableCompletionManager.GetCounters(counters.Other);
- return counters;
- }
- /// <summary>
- /// Gets the client-name that will be used on all new connections
- /// </summary>
- public string ClientName { get { return configuration.ClientName ?? Environment.MachineName; } }
- /// <summary>
- /// Gets the configuration of the connection
- /// </summary>
- public string Configuration
- {
- get { return configuration.ToString(); }
- }
- internal void OnConnectionFailed(EndPoint endpoint, ConnectionType connectionType, ConnectionFailureType failureType, Exception exception, bool reconfigure)
- {
- if (isDisposed) return;
- var handler = ConnectionFailed;
- if (handler != null)
- {
- unprocessableCompletionManager.CompleteSyncOrAsync(
- new ConnectionFailedEventArgs(handler, this, endpoint, connectionType, failureType, exception)
- );
- }
- if (reconfigure)
- {
- ReconfigureIfNeeded(endpoint, false, "connection failed");
- }
- }
- internal void OnInternalError(Exception exception, EndPoint endpoint = null, ConnectionType connectionType = ConnectionType.None, [System.Runtime.CompilerServices.CallerMemberName] string origin = null)
- {
- try
- {
- Trace("Internal error: " + origin + ", " + exception == null ? "unknown" : exception.Message);
- if (isDisposed) return;
- var handler = InternalError;
- if (handler != null)
- {
- unprocessableCompletionManager.CompleteSyncOrAsync(
- new InternalErrorEventArgs(handler, this, endpoint, connectionType, exception, origin)
- );
- }
- }
- catch
- { // our internal error event failed; whatcha gonna do, exactly?
- }
- }
- internal void OnConnectionRestored(EndPoint endpoint, ConnectionType connectionType)
- {
- if (isDisposed) return;
- var handler = ConnectionRestored;
- if (handler != null)
- {
- unprocessableCompletionManager.CompleteSyncOrAsync(
- new ConnectionFailedEventArgs(handler, this, endpoint, connectionType, ConnectionFailureType.None, null)
- );
- }
- ReconfigureIfNeeded(endpoint, false, "connection restored");
- }
- private void OnEndpointChanged(EndPoint endpoint, EventHandler<EndPointEventArgs> handler)
- {
- if (isDisposed) return;
- if (handler != null)
- {
- unprocessableCompletionManager.CompleteSyncOrAsync(
- new EndPointEventArgs(handler, this, endpoint)
- );
- }
- }
- internal void OnConfigurationChanged(EndPoint endpoint)
- {
- OnEndpointChanged(endpoint, ConfigurationChanged);
- }
- internal void OnConfigurationChangedBroadcast(EndPoint endpoint)
- {
- OnEndpointChanged(endpoint, ConfigurationChangedBroadcast);
- }
- /// <summary>
- /// A server replied with an error message;
- /// </summary>
- public event EventHandler<RedisErrorEventArgs> ErrorMessage;
- internal void OnErrorMessage(EndPoint endpoint, string message)
- {
- if (isDisposed) return;
- var handler = ErrorMessage;
- if (handler != null)
- {
- unprocessableCompletionManager.CompleteSyncOrAsync(
- new RedisErrorEventArgs(handler, this, endpoint, message)
- );
- }
- }
- #if !NET40
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
- static void Write<T>(ZipArchive zip, string name, Task task, Action<T, StreamWriter> callback)
- {
- var entry = zip.CreateEntry(name,
- #if __MonoCS__
- CompressionLevel.Fastest
- #else
- CompressionLevel.Optimal
- #endif
- );
- using (var stream = entry.Open())
- using (var writer = new StreamWriter(stream))
- {
- TaskStatus status = task.Status;
- switch (status)
- {
- case TaskStatus.RanToCompletion:
- T val = ((Task<T>)task).Result;
- callback(val, writer);
- break;
- case TaskStatus.Faulted:
- writer.WriteLine(string.Join(", ", task.Exception.InnerExceptions.Select(x => x.Message)));
- break;
- default:
- writer.WriteLine(status.ToString());
- break;
- }
- }
- }
- /// <summary>
- /// Write the configuration of all servers to an output stream
- /// </summary>
- public void ExportConfiguration(Stream destination, ExportOptions options = ExportOptions.All)
- {
- if (destination == null) throw new ArgumentNullException("destination");
- // what is possible, given the command map?
- ExportOptions mask = 0;
- if (CommandMap.IsAvailable(RedisCommand.INFO)) mask |= ExportOptions.Info;
- if (CommandMap.IsAvailable(RedisCommand.CONFIG)) mask |= ExportOptions.Config;
- if (CommandMap.IsAvailable(RedisCommand.CLIENT)) mask |= ExportOptions.Client;
- if (CommandMap.IsAvailable(RedisCommand.CLUSTER)) mask |= ExportOptions.Cluster;
- options &= mask;
- using (var zip = new ZipArchive(destination, ZipArchiveMode.Create, true))
- {
- var arr = serverSnapshot;
- foreach (var server in arr)
- {
- const CommandFlags flags = CommandFlags.None;
- if (!server.IsConnected) continue;
- var api = GetServer(server.EndPoint);
- List<Task> tasks = new List<Task>();
- if ((options & ExportOptions.Info) != 0)
- {
- tasks.Add(api.InfoRawAsync(flags: flags));
- }
- if ((options & ExportOptions.Config) != 0)
- {
- tasks.Add(api.ConfigGetAsync(flags: flags));
- }
- if ((options & ExportOptions.Client) != 0)
- {
- tasks.Add(api.ClientListAsync(flags: flags));
- }
- if ((options & ExportOptions.Cluster) != 0)
- {
- tasks.Add(api.ClusterNodesRawAsync(flags: flags));
- }
- WaitAllIgnoreErrors(tasks.ToArray());
- int index = 0;
- var prefix = Format.ToString(server.EndPoint);
- if ((options & ExportOptions.Info) != 0)
- {
- Write<string>(zip, prefix + "/info.txt", tasks[index++], WriteNormalizingLineEndings);
- }
- if ((options & ExportOptions.Config) != 0)
- {
- Write<KeyValuePair<string, string>[]>(zip, prefix + "/config.txt", tasks[index++], (settings, writer) =>
- {
- foreach (var setting in settings)
- {
- writer.WriteLine("{0}={1}", setting.Key, setting.Value);
- }
- });
- }
- if ((options & ExportOptions.Client) != 0)
- {
- Write<ClientInfo[]>(zip, prefix + "/clients.txt", tasks[index++], (clients, writer) =>
- {
- foreach (var client in clients)
- {
- writer.WriteLine(client.Raw);
- }
- });
- }
- if ((options & ExportOptions.Cluster) != 0)
- {
- Write<string>(zip, prefix + "/nodes.txt", tasks[index++], WriteNormalizingLineEndings);
- }
- }
- }
- }
- #endif
- internal void MakeMaster(ServerEndPoint server, ReplicationChangeOptions options, TextWriter log)
- {
- CommandMap.AssertAvailable(RedisCommand.SLAVEOF);
- if (!configuration.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(IncludeDetailInExceptions, RedisCommand.SLAVEOF, null, server);
- if (server == null) throw new ArgumentNullException("server");
- var srv = new RedisServer(this, server, null);
- if (!srv.IsConnected) throw ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, RedisCommand.SLAVEOF, null, server);
- if (log == null) log = TextWriter.Null;
- CommandMap.AssertAvailable(RedisCommand.SLAVEOF);
- const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.HighPriority;
- Message msg;
- LogLocked(log, "Checking {0} is available...", Format.ToString(srv.EndPoint));
- try
- {
- srv.Ping(flags); // if it isn't happy, we're not happy
- } catch (Exception ex)
- {
- LogLocked(log, "Operation failed on {0}, aborting: {1}", Format.ToString(srv.EndPoint), ex.Message);
- throw;
- }
- var nodes = serverSnapshot;
- RedisValue newMaster = Format.ToString(server.EndPoint);
- RedisKey tieBreakerKey = default(RedisKey);
- // try and write this everywhere; don't worry if some folks reject our advances
- if ((options & ReplicationChangeOptions.SetTiebreaker) != 0 && !string.IsNullOrWhiteSpace(configuration.TieBreaker)
- && CommandMap.IsAvailable(RedisCommand.SET))
- {
- tieBreakerKey = configuration.TieBreaker;
- foreach (var node in nodes)
- {
- if (!node.IsConnected) continue;
- LogLocked(log, "Attempting to set tie-breaker on {0}...", Format.ToString(node.EndPoint));
- msg = Message.Create(0, flags, RedisCommand.SET, tieBreakerKey, newMaster);
- node.QueueDirectFireAndForget(msg, ResultProcessor.DemandOK);
- }
- }
- // deslave...
- LogLocked(log, "Making {0} a master...", Format.ToString(srv.EndPoint));
- try
- {
- srv.SlaveOf(null, flags);
- } catch (Exception ex)
- {
- LogLocked(log, "Operation failed on {0}, aborting: {1}", Format.ToString(srv.EndPoint), ex.Message);
- throw;
- }
- // also, in case it was a slave a moment ago, and hasn't got the tie-breaker yet, we re-send the tie-breaker to this one
- if (!tieBreakerKey.IsNull)
- {
- LogLocked(log, "Resending tie-breaker to {0}...", Format.ToString(server.EndPoint));
- msg = Message.Create(0, flags, RedisCommand.SET, tieBreakerKey, newMaster);
- server.QueueDirectFireAndForget(msg, ResultProcessor.DemandOK);
- }
- // try and broadcast this everywhere, to catch the maximum audience
- if ((options & ReplicationChangeOptions.Broadcast) != 0 && ConfigurationChangedChannel != null
- && CommandMap.IsAvailable(RedisCommand.PUBLISH))
- {
- RedisValue channel = ConfigurationChangedChannel;
- foreach (var node in nodes)
- {
- if (!node.IsConnected) continue;
- LogLocked(log, "Broadcasting via {0}...", Format.ToString(node.EndPoint));
- msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, newMaster);
- node.QueueDirectFireAndForget(msg, ResultProcessor.Int64);
- }
- }
- if ((options & ReplicationChangeOptions.EnslaveSubordinates) != 0)
- {
- foreach (var node in nodes)
- {
- if (node == server || node.ServerType != ServerType.Standalone) continue;
- LogLocked(log, "Enslaving {0}...", Format.ToString(node.EndPoint));
- msg = RedisServer.CreateSlaveOfMessage(server.EndPoint, flags);
- node.QueueDirectFireAndForget(msg, ResultProcessor.DemandOK);
- }
- }
- // and reconfigure the muxer
- LogLocked(log, "Reconfiguring all endpoints...");
- if (!ReconfigureAsync(false, true, log, srv.EndPoint, "make master").ObserveErrors().Wait(5000))
- {
- LogLocked(log, "Verifying the configuration was incomplete; please verify");
- }
- }
- /// <summary>
- /// Used internally to synchronize loggine without depending on locking the log instance
- /// </summary>
- private object LogSyncLock { get { return UniqueId; } } // we know this has strong identity: readonly and unique to us
- internal void LogLocked(TextWriter log, string line)
- {
- lock (LogSyncLock) { log.WriteLine(line); }
- }
- internal void LogLocked(TextWriter log, string line, object arg)
- {
- lock (LogSyncLock) { log.WriteLine(line, arg); }
- }
- internal void LogLocked(TextWriter log, string line, object arg0, object arg1)
- {
- lock (LogSyncLock) { log.WriteLine(line, arg0, arg1); }
- }
- internal void LogLocked(TextWriter log, string line, object arg0, object arg1, object arg2)
- {
- lock (LogSyncLock) { log.WriteLine(line, arg0, arg1, arg2); }
- }
- internal void LogLocked(TextWriter log, string line, params object[] args)
- {
- lock (LogSyncLock) { log.WriteLine(line, args); }
- }
- internal void CheckMessage(Message message)
- {
- if (!configuration.AllowAdmin && message.IsAdmin)
- throw ExceptionFactory.AdminModeNotEnabled(IncludeDetailInExceptions, message.Command, message, null);
- CommandMap.AssertAvailable(message.Command);
- }
- static void WriteNormalizingLineEndings(string source, StreamWriter writer)
- {
- using (var reader = new StringReader(source))
- {
- string line;
- while ((line = reader.ReadLine()) != null)
- writer.WriteLine(line); // normalize line endings
- }
- }
- /// <summary>
- /// Raised whenever a physical connection fails
- /// </summary>
- public event EventHandler<ConnectionFailedEventArgs> ConnectionFailed;
- /// <summary>
- /// Raised whenever an internal error occurs (this is primarily for debugging)
- /// </summary>
- public event EventHandler<InternalErrorEventArgs> InternalError;
- /// <summary>
- /// Raised whenever a physical connection is established
- /// </summary>
- public event EventHandler<ConnectionFailedEventArgs> ConnectionRestored;
- /// <summary>
- /// Raised when configuration changes are detected
- /// </summary>
- public event EventHandler<EndPointEventArgs> ConfigurationChanged;
- /// <summary>
- /// Raised when nodes are explicitly requested to reconfigure via broadcast;
- /// this usually means master/slave changes
- /// </summary>
- public event EventHandler<EndPointEventArgs> ConfigurationChangedBroadcast;
- /// <summary>
- /// Gets the timeout associated with the connections
- /// </summary>
- public int TimeoutMilliseconds
- {
- get { return timeoutMilliseconds; }
- }
- /// <summary>
- /// Gets all endpoints defined on the server
- /// </summary>
- /// <returns></returns>
- public EndPoint[] GetEndPoints(bool configuredOnly = false)
- {
- if (configuredOnly) return configuration.EndPoints.ToArray();
- return Array.ConvertAll(serverSnapshot, x => x.EndPoint);
- }
- private readonly int timeoutMilliseconds;
- private readonly ConfigurationOptions configuration;
- internal bool TryResend(int hashSlot, Message message, EndPoint endpoint, bool isMoved)
- {
- return serverSelectionStrategy.TryResend(hashSlot, message, endpoint, isMoved);
- }
- /// <summary>
- /// Wait for a given asynchronous operation to complete (or timeout)
- /// </summary>
- public void Wait(Task task)
- {
- if (task == null) throw new ArgumentNullException("task");
- if (!task.Wait(timeoutMilliseconds)) throw new TimeoutException();
- }
- /// <summary>
- /// Wait for a given asynchronous operation to complete (or timeout)
- /// </summary>
- public T Wait<T>(Task<T> task)
- {
- if (task == null) throw new ArgumentNullException("task");
- if (!task.Wait(timeoutMilliseconds)) throw new TimeoutException();
- return task.Result;
- }
- /// <summary>
- /// Wait for the given asynchronous operations to complete (or timeout)
- /// </summary>
- public void WaitAll(params Task[] tasks)
- {
- if (tasks == null) throw new ArgumentNullException("tasks");
- if (tasks.Length == 0) return;
- if (!Task.WaitAll(tasks, timeoutMilliseconds)) throw new TimeoutException();
- }
- private bool WaitAllIgnoreErrors(Task[] tasks)
- {
- return WaitAllIgnoreErrors(tasks, timeoutMilliseconds);
- }
- private static bool WaitAllIgnoreErrors(Task[] tasks, int timeout)
- {
- if (tasks == null) throw new ArgumentNullException("tasks");
- if (tasks.Length == 0) return true;
- var watch = Stopwatch.StartNew();
- try
- {
- // if none error, great
- if (Task.WaitAll(tasks, timeout)) return true;
- }
- catch
- { }
- // if we get problems, need to give the non-failing ones time to finish
- // to be fair and reasonable
- for (int i = 0; i < tasks.Length; i++)
- {
- var task = tasks[i];
- if (!task.IsCanceled && !task.IsCompleted && !task.IsFaulted)
- {
- var remaining = timeout - checked((int)watch.ElapsedMilliseconds);
- if (remaining <= 0) return false;
- try
- {
- task.Wait(remaining);
- }
- catch
- { }
- }
- }
- return false;
- }
- private static async Task<bool> WaitAllIgnoreErrorsAsync(Task[] tasks, int timeoutMilliseconds)
- {
- if (tasks == null) throw new ArgumentNullException("tasks");
- if (tasks.Length == 0) return true;
- var watch = Stopwatch.StartNew();
- try
- {
- // if none error, great
- #if NET40
- var allTasks = TaskEx.WhenAll(tasks).ObserveErrors();
- var any = TaskEx.WhenAny(allTasks, TaskEx.Delay(timeoutMilliseconds)).ObserveErrors();
- #else
- var allTasks = Task.WhenAll(tasks).ObserveErrors();
- var any = Task.WhenAny(allTasks, Task.Delay(timeoutMilliseconds)).ObserveErrors();
- #endif
- return await any.ForAwait() == allTasks;
- }
- catch
- { }
- // if we get problems, need to give the non-failing ones time to finish
- // to be fair and reasonable
- for (int i = 0; i < tasks.Length; i++)
- {
- var task = tasks[i];
- if (!task.IsCanceled && !task.IsCompleted && !task.IsFaulted)
- {
- var remaining = timeoutMilliseconds - checked((int)watch.ElapsedMilliseconds);
- if (remaining <= 0) return false;
- try
- {
- #if NET40
- var any = TaskEx.WhenAny(task, TaskEx.Delay(remaining)).ObserveErrors();
- #else
- var any = Task.WhenAny(task, Task.Delay(remaining)).ObserveErrors();
- #endif
- await any.ForAwait();
- }
- catch
- { }
- }
- }
- return false;
- }
- /// <summary>
- /// Raised when a hash-slot has been relocated
- /// </summary>
- public event EventHandler<HashSlotMovedEventArgs> HashSlotMoved;
- internal void OnHashSlotMoved(int hashSlot, EndPoint old, EndPoint @new)
- {
- var handler = HashSlotMoved;
- if (handler != null)
- {
- unprocessableCompletionManager.CompleteSyncOrAsync(
- new HashSlotMovedEventArgs(handler, this, hashSlot, old, @new)
- );
- }
- }
- /// <summary>
- /// Compute the hash-slot of a specified key
- /// </summary>
- public int HashSlot(RedisKey key)
- {
- return serverSelectionStrategy.HashSlot(key);
- }
- internal ServerEndPoint AnyConnected(ServerType serverType, uint startOffset, RedisCommand command, CommandFlags flags)
- {
- var tmp = serverSnapshot;
- int len = tmp.Length;
- ServerEndPoint fallback = null;
- for (int i = 0; i < len; i++)
- {
- var server = tmp[(int)(((uint)i + startOffset) % len)];
- if (server.ServerType == serverType && server.IsSelectable(command))
- {
- if (server.IsSlave)
- {
- switch (flags)
- {
- case CommandFlags.DemandSlave:
- case CommandFlags.PreferSlave:
- return server;
- case CommandFlags.PreferMaster:
- fallback = server;
- break;
- }
- } else
- {
- switch (flags)
- {
- case CommandFlags.DemandMaster:
- case CommandFlags.PreferMaster:
- return server;
- case CommandFlags.PreferSlave:
- fallback = server;
- break;
- }
- }
- }
- }
- return fallback;
- }
- volatile bool isDisposed;
- internal bool IsDisposed { get { return isDisposed; } }
- /// <summary>
- /// Create a new ConnectionMultiplexer instance
- /// </summary>
- public static async Task<ConnectionMultiplexer> ConnectAsync(string configuration, TextWriter log = null)
- {
- IDisposable killMe = null;
- try
- {
- var muxer = CreateMultiplexer(configuration);
- killMe = muxer;
- bool configured = await muxer.ReconfigureAsync(true, false, log, null, "connect").ObserveErrors().ForAwait();
- if (!configured)
- {
- throw ExceptionFactory.UnableToConnect(muxer.failureMessage);
- }
- killMe = null;
- return muxer;
- } finally
- {
- if (killMe != null) try { killMe.Dispose(); } catch { }
- }
- }
- /// <summary>
- /// Create a new ConnectionMultiplexer instance
- /// </summary>
- public static async Task<ConnectionMultiplexer> ConnectAsync(ConfigurationOptions configuration, TextWriter log = null)
- {
- IDisposable killMe = null;
- try
- {
- var muxer = CreateMultiplexer(configuration);
- killMe = muxer;
- bool configured = await muxer.ReconfigureAsync(true, false, log, null, "connect").ObserveErrors().ForAwait();
- if (!configured)
- {
- throw ExceptionFactory.UnableToConnect(muxer.failureMessage);
- }
- killMe = null;
- return muxer;
- } finally
- {
- if (killMe != null) try { killMe.Dispose(); } catch { }
- }
- }
- static ConnectionMultiplexer CreateMultiplexer(object configuration)
- {
- if (configuration == null) throw new ArgumentNullException("configuration");
- ConfigurationOptions config;
- if (configuration is string)
- {
- config = ConfigurationOptions.Parse((string)configuration);
- } else if (configuration is ConfigurationOptions)
- {
- config = ((ConfigurationOptions)configuration).Clone();
- } else
- {
- throw new ArgumentException("configuration");
- }
- if (config.EndPoints.Count == 0) throw new ArgumentException("No endpoints specified", "configuration");
- config.SetDefaultPorts();
- return new ConnectionMultiplexer(config);
- }
- /// <summary>
- /// Create a new ConnectionMultiplexer instance
- /// </summary>
- public static ConnectionMultiplexer Connect(string configuration, TextWriter log = null)
- {
- IDisposable killMe = null;
- try
- {
- var muxer = CreateMultiplexer(configuration);
- killMe = muxer;
- // note that task has timeouts internally, so it might take *just over* the reegular timeout
- var task = muxer.ReconfigureAsync(true, false, log, null, "connect");
- if (!task.Wait(muxer.SyncConnectTimeout(true)))
- {
- task.ObserveErrors();
- if (muxer.RawConfig.AbortOnConnectFail)
- {
- throw new TimeoutException();
- }
- }
- if(!task.Result) throw ExceptionFactory.UnableToConnect(muxer.failureMessage);
- killMe = null;
- return muxer;
- }
- finally
- {
- if (killMe != null) try { killMe.Dispose(); } catch { }
- }
- }
- /// <summary>
- /// Create a new ConnectionMultiplexer instance
- /// </summary>
- public static ConnectionMultiplexer Connect(ConfigurationOptions configuration, TextWriter log = null)
- {
- IDisposable killMe = null;
- try
- {
- var muxer = CreateMultiplexer(configuration);
- killMe = muxer;
- // note that task has timeouts internally, so it might take *just over* the reegular timeout
- var task = muxer.ReconfigureAsync(true, false, log, null, "connect");
- if (!task.Wait(muxer.SyncConnectTimeout(true)))
- {
- task.ObserveErrors();
- if (muxer.RawConfig.AbortOnConnectFail)
- {
- throw new TimeoutException();
- }
- }
- if (!task.Result) throw ExceptionFactory.UnableToConnect(muxer.failureMessage);
- killMe = null;
- return muxer;
- }
- finally
- {
- if (killMe != null) try { killMe.Dispose(); } catch { }
- }
- }
- private string failureMessage;
- private readonly Hashtable servers = new Hashtable();
- private volatile ServerEndPoint[] serverSnapshot = NilServers;
- private static readonly ServerEndPoint[] NilServers = new ServerEndPoint[0];
- internal ServerEndPoint GetServerEndPoint(EndPoint endpoint)
- {
- if (endpoint == null) return null;
- var server = (ServerEndPoint)servers[endpoint];
- if (server == null)
- {
- lock (servers)
- {
- server = (ServerEndPoint)servers[endpoint];
- if (server == null)
- {
- if (isDisposed) throw new ObjectDisposedException(ToString());
- server = new ServerEndPoint(this, endpoint);
- servers.Add(endpoint, server);
- var newSnapshot = serverSnapshot;
- Array.Resize(ref newSnapshot, newSnapshot.Length + 1);
- newSnapshot[newSnapshot.Length - 1] = server;
- serverSnapshot = newSnapshot;
- }
- }
- }
- return server;
- }
- internal readonly CommandMap CommandMap;
- private ConnectionMultiplexer(ConfigurationOptions configuration)
- {
- if (configuration == null) throw new ArgumentNullException("configuration");
- IncludeDetailInExceptions = true;
-
- this.configuration = configuration;
-
- var map = this.CommandMap = configuration.CommandMap;
- if (!string.IsNullOrWhiteSpace(configuration.Password)) map.AssertAvailable(RedisCommand.AUTH);
- if(!map.IsAvailable(RedisCommand.ECHO) && !map.IsAvailable(RedisCommand.PING) && !map.IsAvailable(RedisCommand.TIME))
- { // I mean really, give me a CHANCE! I need *something* to check the server is available to me...
- // see also: SendTracer (matching logic)
- map.AssertAvailable(RedisCommand.EXISTS);
- }
- PreserveAsyncOrder = true; // safest default
- this.timeoutMilliseconds = configuration.SyncTimeout;
- OnCreateReaderWriter(configuration);
- unprocessableCompletionManager = new CompletionManager(this, "multiplexer");
- serverSelectionStrategy = new ServerSelectionStrategy(this);
- var configChannel = configuration.ConfigurationChannel;
- if (!string.IsNullOrWhiteSpace(configChannel))
- {
- ConfigurationChangedChannel = Encoding.UTF8.GetBytes(configChannel);
- }
- lastHeartbeatTicks = Environment.TickCount;
- }
- partial void OnCreateReaderWriter(ConfigurationOptions configuration);
- internal const int MillisecondsPerHeartbeat = 1000;
- private static readonly TimerCallback heartbeat = state =>
- {
- ((ConnectionMultiplexer)state).OnHeartbeat();
- };
- private void OnHeartbeat()
- {
- try
- {
- int now = Environment.TickCount;
- Interlocked.Exchange(ref lastHeartbeatTicks, now);
- Interlocked.Exchange(ref lastGlobalHeartbeatTicks, now);
- Trace("heartbeat");
- var tmp = serverSnapshot;
- for (int i = 0; i < tmp.Length; i++)
- tmp[i].OnHeartbeat();
- } catch(Exception ex)
- {
- OnInternalError(ex);
- }
- }
- private int lastHeartbeatTicks;
- private static int lastGlobalHeartbeatTicks = Environment.TickCount;
- internal long LastHeartbeatSecondsAgo {
- get {
- if (pulse == null) return -1;
- return unchecked(Environment.TickCount - Thread.VolatileRead(ref lastHeartbeatTicks)) / 1000;
- }
- }
- internal static long LastGlobalHeartbeatSecondsAgo
- { get { return unchecked(Environment.TickCount - Thread.VolatileRead(ref lastGlobalHeartbeatTicks)) / 1000; } }
- internal CompletionManager UnprocessableCompletionManager { get { return unprocessableCompletionManager; } }
- /// <summary>
- /// Obtain a pub/sub subscriber connection to the specified server
- /// </summary>
- public ISubscriber GetSubscriber(object asyncState = null)
- {
- if (RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("The pub/sub API is not available via twemproxy");
- return new RedisSubscriber(this, asyncState);
- }
- /// <summary>
- /// Obtain an interactive connection to a database inside redis
- /// </summary>
- public IDatabase GetDatabase(int db = 0, object asyncState = null)
- {
- if (db < 0) throw new ArgumentOutOfRangeException("db");
- if (db != 0 && RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("Twemproxy only supports database 0");
- return new RedisDatabase(this, db, asyncState);
- }
- /// <summary>
- /// Obtain a configuration API for an individual server
- /// </summary>
- public IServer GetServer(string host, int port, object asyncState = null)
- {
- return GetServer(Format.ParseEndPoint(host, port), asyncState);
- }
- /// <summary>
- /// Obtain a configuration API for an individual server
- /// </summary>
- public IServer GetServer(string hostAndPort, object asyncState = null)
- {
- return GetServer(Format.TryParseEndPoint(hostAndPort), asyncState);
- }
- /// <summary>
- /// Obtain a configuration API for an individual server
- /// </summary>
- public IServer GetServer(IPAddress host, int port)
- {
- return GetServer(new IPEndPoint(host, port));
- }
- /// <summary>
- /// Obtain a configuration API for an individual server
- /// </summary>
- public IServer GetServer(EndPoint endpoint, object asyncState = null)
- {
- if (endpoint == null) throw new ArgumentNullException("endpoint");
- if (RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("The server API is not available via twemproxy");
- var server = (ServerEndPoint)servers[endpoint];
- if (server == null) throw new ArgumentException("The specified endpoint is not defined", "endpoint");
- return new RedisServer(this, server, asyncState);
- }
- [Conditional("VERBOSE")]
- internal void Trace(string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null)
- {
- OnTrace(message, category);
- }
- [Conditional("VERBOSE")]
- internal void Trace(bool condition, string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null)
- {
- if (condition) OnTrace(message, category);
- }
- partial void OnTrace(string message, string category);
- static partial void OnTraceWithoutContext(string message, string category);
- [Conditional("VERBOSE")]
- internal static void TraceWithoutContext(string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null)
- {
- OnTraceWithoutContext(message, category);
- }
- [Conditional("VERBOSE")]
- internal static void TraceWithoutContext(bool condition, string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null)
- {
- if(condition) OnTraceWithoutContext(message, category);
- }
- private readonly CompletionManager unprocessableCompletionManager;
- /// <summary>
- /// The number of operations that have been performed on all connections
- /// </summary>
- public long OperationCount {
- get
- {
- long total = 0;
- var snapshot = serverSnapshot;
- for (int i = 0; i < snapshot.Length; i++) total += snapshot[i].OperationCount;
- return total;
- }
- }
- string activeConfigCause;
- internal void ReconfigureIfNeeded(EndPoint blame, bool fromBroadcast, string cause)
- {
- if (fromBroadcast)
- {
- OnConfigurationChangedBroadcast(blame);
- }
- string activeCause = Interlocked.CompareExchange(ref activeConfigCause, null, null);
- if (activeCause == null)
- {
- bool reconfigureAll = fromBroadcast;
- Trace("Configuration change detected; checking nodes", "Configuration");
- ReconfigureAsync(false, reconfigureAll, null, blame, cause).ObserveErrors();
- } else
- {
- Trace("Configuration change skipped; already in progress via " + activeCause, "Configuration");
- }
- }
- /// <summary>
- /// Reconfigure the current connections based on the existing configuration
- /// </summary>
- public Task<bool> ConfigureAsync(TextWriter log = null)
- {
- return ReconfigureAsync(false, true, log, null, "configure").ObserveErrors();
- }
- /// <summary>
- /// Reconfigure the current connections based on the existing configuration
- /// </summary>
- public bool Configure(TextWriter log = null)
- {
- // note we expect ReconfigureAsync to internally allow [n] duration,
- // so to avoid near misses, here we wait 2*[n]
- var task = ReconfigureAsync(false, true, log, null, "configure");
- if (!task.Wait(SyncConnectTimeout(false)))
- {
- task.ObserveErrors();
- if (configuration.AbortOnConnectFail)
- {
- throw new TimeoutException();
- }
- return false;
- }
- return task.Result;
- }
- internal int SyncConnectTimeout(bool forConnect)
- {
- int retryCount = forConnect ? RawConfig.ConnectRetry : 1;
- if (retryCount <= 0) retryCount = 1;
- int timeout = configuration.ConnectTimeout;
- if (timeout >= int.MaxValue / retryCount) return int.MaxValue;
- timeout *= retryCount;
- if (timeout >= int.MaxValue - 500) return int.MaxValue;
- return timeout + Math.Min(500, timeout);
- }
- /// <summary>
- /// Provides a text overview of the status of all connections
- /// </summary>
- public string GetStatus()
- {
- using(var sw = new StringWriter())
- {
- GetStatus(sw);
- return sw.ToString();
- }
- }
- /// <summary>
- /// Provides a text overview of the status of all connections
- /// </summary>
- public void GetStatus(TextWriter log)
- {
- if (log == null) return;
- var tmp = serverSnapshot;
- foreach (var server in tmp)
- {
- LogLocked(log, server.Summary());
- LogLocked(log, server.GetCounters().ToString());
- LogLocked(log, server.GetProfile());
- }
- LogLocked(log, "Sync timeouts: {0}; fire and forget: {1}; last heartbeat: {2}s ago",
- Interlocked.Read(ref syncTimeouts), Interlocked.Read(ref fireAndForgets), LastHeartbeatSecondsAgo);
- }
- internal async Task<bool> ReconfigureAsync(bool first, bool reconfigureAll, TextWriter log, EndPoint blame, string cause)
- {
- if (isDisposed) throw new ObjectDisposedException(ToString());
- bool showStats = true;
- if (log == null)
- {
- log = TextWriter.Null;
- showStats = false;
- }
- bool ranThisCall = false;
- try
- { // note that "activeReconfigs" starts at one; we don't need to set it the first time
- ranThisCall = first || Interlocked.CompareExchange(ref activeConfigCause, cause, null) == null;
- if (!ranThisCall)
- {
- LogLocked(log, "Reconfiguration was already in progress");
- return false;
- }
- Trace("Starting reconfiguration...");
- Trace(blame != null, "Blaming: " + Format.ToString(blame));
- LogLocked(log, Configuration);
- LogLocked(log, "");
- if (first)
- {
- if (configuration.ResolveDns && configuration.HasDnsEndPoints())
- {
- var dns = configuration.ResolveEndPointsAsync(this, log).ObserveErrors();
- #if NET40
- var any = TaskEx.WhenAny(dns, TaskEx.Delay(timeoutMilliseconds));
- #else
- var any = Task.WhenAny(dns, Task.Delay(timeoutMilliseconds));
- #endif
- if ((await any.ForAwait()) != dns)
- {
- throw new TimeoutException("Timeout resolving endpoints");
- }
- }
- int index = 0;
- lock (this.servers)
- {
- serverSnapshot = new ServerEndPoint[configuration.EndPoints.Count];
- foreach (var endpoint in configuration.EndPoints)
- {
- var server = new ServerEndPoint(this, endpoint);
- serverSnapshot[index++] = server;
- this.servers.Add(endpoint, server);
- }
- }
- foreach (var server in serverSnapshot)
- {
- server.Activate(ConnectionType.Interactive);
- if (this.CommandMap.IsAvailable(RedisCommand.SUBSCRIBE))
- {
- server.Activate(ConnectionType.Subscription);
- }
- }
- }
- int attemptsLeft = first ? configuration.ConnectRetry : 1;
- bool healthy = false;
- do
- {
- if (first)
- {
- attemptsLeft--;
- }
- int standaloneCount = 0, clusterCount = 0;
- var endpoints = configuration.EndPoints;
- LogLocked(log, "{0} unique nodes specified", endpoints.Count);
- if (endpoints.Count == 0)
- {
- throw new InvalidOperationException("No nodes to consider");
- }
- const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.HighPriority;
- var available = new Task<bool>[endpoints.Count];
- var servers = new ServerEndPoint[available.Length];
- bool useTieBreakers = !string.IsNullOrWhiteSpace(configuration.TieBreaker);
- var tieBreakers = useTieBreakers ? new Task<string>[endpoints.Count] : null;
- RedisKey tieBreakerKey = useTieBreakers ? (RedisKey)configuration.TieBreaker : default(RedisKey);
- for (int i = 0; i < available.Length; i++)
- {
- Trace("Testing: " + Format.ToString(endpoints[i]));
- var server = GetServerEndPoint(endpoints[i]);
- //server.ReportNextFailure();
- servers[i] = server;
- if (reconfigureAll && server.IsConnected)
- {
- LogLocked(log, "Refreshing {0}...", Format.ToString(server.EndPoint));
- // note that these will be processed synchronously *BEFORE* the tracer is processed,
- // so we know that the configuration will be up to date if we see the tracer
- server.AutoConfigure(null);
- }
- available[i] = server.SendTracer();
- Message msg;
- if (useTieBreakers)
- {
- LogLocked(log, "Requesting tie-break from {0} > {1}...", Format.ToString(server.EndPoint), configuration.TieBreaker);
- msg = Message.Create(0, flags, RedisCommand.GET, tieBreakerKey);
- msg.SetInternalCall();
- tieBreakers[i] = server.QueueDirectAsync(msg, ResultProcessor.String);
- }
- }
- LogLocked(log, "Allowing endpoints {0} to respond...", TimeSpan.FromMilliseconds(configuration.ConnectTimeout));
- Trace("Allowing endpoints " + TimeSpan.FromMilliseconds(configuration.ConnectTimeout) + " to respond...");
- await WaitAllIgnoreErrorsAsync(available, configuration.ConnectTimeout).ForAwait();
- List<ServerEndPoint> masters = new List<ServerEndPoint>(available.Length);
- for (int i = 0; i < available.Length; i++)
- {
- var task = available[i];
- Trace(Format.ToString(endpoints[i]) + ": " + task.Status);
- if (task.IsFaulted)
- {
- servers[i].SetUnselectable(UnselectableFlags.DidNotRespond);
- var aex = task.Exception;
- foreach (var ex in aex.InnerExceptions)
- {
- LogLocked(log, "{0} faulted: {1}", Format.ToString(endpoints[i]), ex.Message);
- failureMessage = ex.Message;
- }
- }
- else if (task.IsCanceled)
- {
- servers[i].SetUnselectable(UnselectableFlags.DidNotRespond);
- LogLocked(log, "{0} was canceled", Format.ToString(endpoints[i]));
- }
- else if (task.IsCompleted)
- {
- var server = servers[i];
- if (task.Result)
- {
- servers[i].ClearUnselectable(UnselectableFlags.DidNotRespond);
- LogLocked(log, "{0} returned with success", Format.ToString(endpoints[i]));
- switch (server.ServerType)
- {
- case ServerType.Twemproxy:
- case ServerType.Standalone:
- servers[i].ClearUnselectable(UnselectableFlags.ServerType);
- standaloneCount++;
- if (server.IsSlave)
- {
- servers[i].ClearUnselectable(UnselectableFlags.RedundantMaster);
- }
- else
- {
- masters.Add(server);
- }
- break;…