/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

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.IO.Compression;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. #if NET40
  13. using Microsoft.Runtime.CompilerServices;
  14. #else
  15. using System.Runtime.CompilerServices;
  16. #endif
  17. namespace StackExchange.Redis
  18. {
  19. internal static partial class TaskExtensions
  20. {
  21. private static readonly Action<Task> observeErrors = ObverveErrors;
  22. private static void ObverveErrors(this Task task)
  23. {
  24. if (task != null) GC.KeepAlive(task.Exception);
  25. }
  26. public static Task ObserveErrors(this Task task)
  27. {
  28. if (task != null) task.ContinueWith(observeErrors, TaskContinuationOptions.OnlyOnFaulted);
  29. return task;
  30. }
  31. public static Task<T> ObserveErrors<T>(this Task<T> task)
  32. {
  33. if (task != null) task.ContinueWith(observeErrors, TaskContinuationOptions.OnlyOnFaulted);
  34. return task;
  35. }
  36. public static ConfiguredTaskAwaitable ForAwait(this Task task)
  37. {
  38. return task.ConfigureAwait(false);
  39. }
  40. public static ConfiguredTaskAwaitable<T> ForAwait<T>(this Task<T> task)
  41. {
  42. return task.ConfigureAwait(false);
  43. }
  44. }
  45. /// <summary>
  46. /// Represents an inter-related group of connections to redis servers
  47. /// </summary>
  48. public sealed partial class ConnectionMultiplexer : IDisposable
  49. {
  50. /// <summary>
  51. /// Get summary statistics associates with this server
  52. /// </summary>
  53. public ServerCounters GetCounters()
  54. {
  55. var snapshot = serverSnapshot;
  56. var counters = new ServerCounters(null);
  57. for (int i = 0; i < snapshot.Length; i++)
  58. {
  59. counters.Add(snapshot[i].GetCounters());
  60. }
  61. unprocessableCompletionManager.GetCounters(counters.Other);
  62. return counters;
  63. }
  64. /// <summary>
  65. /// Gets the client-name that will be used on all new connections
  66. /// </summary>
  67. public string ClientName { get { return configuration.ClientName ?? Environment.MachineName; } }
  68. /// <summary>
  69. /// Gets the configuration of the connection
  70. /// </summary>
  71. public string Configuration
  72. {
  73. get { return configuration.ToString(); }
  74. }
  75. internal void OnConnectionFailed(EndPoint endpoint, ConnectionType connectionType, ConnectionFailureType failureType, Exception exception, bool reconfigure)
  76. {
  77. if (isDisposed) return;
  78. var handler = ConnectionFailed;
  79. if (handler != null)
  80. {
  81. unprocessableCompletionManager.CompleteSyncOrAsync(
  82. new ConnectionFailedEventArgs(handler, this, endpoint, connectionType, failureType, exception)
  83. );
  84. }
  85. if (reconfigure)
  86. {
  87. ReconfigureIfNeeded(endpoint, false, "connection failed");
  88. }
  89. }
  90. internal void OnInternalError(Exception exception, EndPoint endpoint = null, ConnectionType connectionType = ConnectionType.None, [System.Runtime.CompilerServices.CallerMemberName] string origin = null)
  91. {
  92. try
  93. {
  94. Trace("Internal error: " + origin + ", " + exception == null ? "unknown" : exception.Message);
  95. if (isDisposed) return;
  96. var handler = InternalError;
  97. if (handler != null)
  98. {
  99. unprocessableCompletionManager.CompleteSyncOrAsync(
  100. new InternalErrorEventArgs(handler, this, endpoint, connectionType, exception, origin)
  101. );
  102. }
  103. }
  104. catch
  105. { // our internal error event failed; whatcha gonna do, exactly?
  106. }
  107. }
  108. internal void OnConnectionRestored(EndPoint endpoint, ConnectionType connectionType)
  109. {
  110. if (isDisposed) return;
  111. var handler = ConnectionRestored;
  112. if (handler != null)
  113. {
  114. unprocessableCompletionManager.CompleteSyncOrAsync(
  115. new ConnectionFailedEventArgs(handler, this, endpoint, connectionType, ConnectionFailureType.None, null)
  116. );
  117. }
  118. ReconfigureIfNeeded(endpoint, false, "connection restored");
  119. }
  120. private void OnEndpointChanged(EndPoint endpoint, EventHandler<EndPointEventArgs> handler)
  121. {
  122. if (isDisposed) return;
  123. if (handler != null)
  124. {
  125. unprocessableCompletionManager.CompleteSyncOrAsync(
  126. new EndPointEventArgs(handler, this, endpoint)
  127. );
  128. }
  129. }
  130. internal void OnConfigurationChanged(EndPoint endpoint)
  131. {
  132. OnEndpointChanged(endpoint, ConfigurationChanged);
  133. }
  134. internal void OnConfigurationChangedBroadcast(EndPoint endpoint)
  135. {
  136. OnEndpointChanged(endpoint, ConfigurationChangedBroadcast);
  137. }
  138. /// <summary>
  139. /// A server replied with an error message;
  140. /// </summary>
  141. public event EventHandler<RedisErrorEventArgs> ErrorMessage;
  142. internal void OnErrorMessage(EndPoint endpoint, string message)
  143. {
  144. if (isDisposed) return;
  145. var handler = ErrorMessage;
  146. if (handler != null)
  147. {
  148. unprocessableCompletionManager.CompleteSyncOrAsync(
  149. new RedisErrorEventArgs(handler, this, endpoint, message)
  150. );
  151. }
  152. }
  153. #if !NET40
  154. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
  155. static void Write<T>(ZipArchive zip, string name, Task task, Action<T, StreamWriter> callback)
  156. {
  157. var entry = zip.CreateEntry(name,
  158. #if __MonoCS__
  159. CompressionLevel.Fastest
  160. #else
  161. CompressionLevel.Optimal
  162. #endif
  163. );
  164. using (var stream = entry.Open())
  165. using (var writer = new StreamWriter(stream))
  166. {
  167. TaskStatus status = task.Status;
  168. switch (status)
  169. {
  170. case TaskStatus.RanToCompletion:
  171. T val = ((Task<T>)task).Result;
  172. callback(val, writer);
  173. break;
  174. case TaskStatus.Faulted:
  175. writer.WriteLine(string.Join(", ", task.Exception.InnerExceptions.Select(x => x.Message)));
  176. break;
  177. default:
  178. writer.WriteLine(status.ToString());
  179. break;
  180. }
  181. }
  182. }
  183. /// <summary>
  184. /// Write the configuration of all servers to an output stream
  185. /// </summary>
  186. public void ExportConfiguration(Stream destination, ExportOptions options = ExportOptions.All)
  187. {
  188. if (destination == null) throw new ArgumentNullException("destination");
  189. // what is possible, given the command map?
  190. ExportOptions mask = 0;
  191. if (CommandMap.IsAvailable(RedisCommand.INFO)) mask |= ExportOptions.Info;
  192. if (CommandMap.IsAvailable(RedisCommand.CONFIG)) mask |= ExportOptions.Config;
  193. if (CommandMap.IsAvailable(RedisCommand.CLIENT)) mask |= ExportOptions.Client;
  194. if (CommandMap.IsAvailable(RedisCommand.CLUSTER)) mask |= ExportOptions.Cluster;
  195. options &= mask;
  196. using (var zip = new ZipArchive(destination, ZipArchiveMode.Create, true))
  197. {
  198. var arr = serverSnapshot;
  199. foreach (var server in arr)
  200. {
  201. const CommandFlags flags = CommandFlags.None;
  202. if (!server.IsConnected) continue;
  203. var api = GetServer(server.EndPoint);
  204. List<Task> tasks = new List<Task>();
  205. if ((options & ExportOptions.Info) != 0)
  206. {
  207. tasks.Add(api.InfoRawAsync(flags: flags));
  208. }
  209. if ((options & ExportOptions.Config) != 0)
  210. {
  211. tasks.Add(api.ConfigGetAsync(flags: flags));
  212. }
  213. if ((options & ExportOptions.Client) != 0)
  214. {
  215. tasks.Add(api.ClientListAsync(flags: flags));
  216. }
  217. if ((options & ExportOptions.Cluster) != 0)
  218. {
  219. tasks.Add(api.ClusterNodesRawAsync(flags: flags));
  220. }
  221. WaitAllIgnoreErrors(tasks.ToArray());
  222. int index = 0;
  223. var prefix = Format.ToString(server.EndPoint);
  224. if ((options & ExportOptions.Info) != 0)
  225. {
  226. Write<string>(zip, prefix + "/info.txt", tasks[index++], WriteNormalizingLineEndings);
  227. }
  228. if ((options & ExportOptions.Config) != 0)
  229. {
  230. Write<KeyValuePair<string, string>[]>(zip, prefix + "/config.txt", tasks[index++], (settings, writer) =>
  231. {
  232. foreach (var setting in settings)
  233. {
  234. writer.WriteLine("{0}={1}", setting.Key, setting.Value);
  235. }
  236. });
  237. }
  238. if ((options & ExportOptions.Client) != 0)
  239. {
  240. Write<ClientInfo[]>(zip, prefix + "/clients.txt", tasks[index++], (clients, writer) =>
  241. {
  242. foreach (var client in clients)
  243. {
  244. writer.WriteLine(client.Raw);
  245. }
  246. });
  247. }
  248. if ((options & ExportOptions.Cluster) != 0)
  249. {
  250. Write<string>(zip, prefix + "/nodes.txt", tasks[index++], WriteNormalizingLineEndings);
  251. }
  252. }
  253. }
  254. }
  255. #endif
  256. internal void MakeMaster(ServerEndPoint server, ReplicationChangeOptions options, TextWriter log)
  257. {
  258. CommandMap.AssertAvailable(RedisCommand.SLAVEOF);
  259. if (!configuration.AllowAdmin) throw ExceptionFactory.AdminModeNotEnabled(IncludeDetailInExceptions, RedisCommand.SLAVEOF, null, server);
  260. if (server == null) throw new ArgumentNullException("server");
  261. var srv = new RedisServer(this, server, null);
  262. if (!srv.IsConnected) throw ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, RedisCommand.SLAVEOF, null, server);
  263. if (log == null) log = TextWriter.Null;
  264. CommandMap.AssertAvailable(RedisCommand.SLAVEOF);
  265. const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.HighPriority;
  266. Message msg;
  267. LogLocked(log, "Checking {0} is available...", Format.ToString(srv.EndPoint));
  268. try
  269. {
  270. srv.Ping(flags); // if it isn't happy, we're not happy
  271. } catch (Exception ex)
  272. {
  273. LogLocked(log, "Operation failed on {0}, aborting: {1}", Format.ToString(srv.EndPoint), ex.Message);
  274. throw;
  275. }
  276. var nodes = serverSnapshot;
  277. RedisValue newMaster = Format.ToString(server.EndPoint);
  278. RedisKey tieBreakerKey = default(RedisKey);
  279. // try and write this everywhere; don't worry if some folks reject our advances
  280. if ((options & ReplicationChangeOptions.SetTiebreaker) != 0 && !string.IsNullOrWhiteSpace(configuration.TieBreaker)
  281. && CommandMap.IsAvailable(RedisCommand.SET))
  282. {
  283. tieBreakerKey = configuration.TieBreaker;
  284. foreach (var node in nodes)
  285. {
  286. if (!node.IsConnected) continue;
  287. LogLocked(log, "Attempting to set tie-breaker on {0}...", Format.ToString(node.EndPoint));
  288. msg = Message.Create(0, flags, RedisCommand.SET, tieBreakerKey, newMaster);
  289. node.QueueDirectFireAndForget(msg, ResultProcessor.DemandOK);
  290. }
  291. }
  292. // deslave...
  293. LogLocked(log, "Making {0} a master...", Format.ToString(srv.EndPoint));
  294. try
  295. {
  296. srv.SlaveOf(null, flags);
  297. } catch (Exception ex)
  298. {
  299. LogLocked(log, "Operation failed on {0}, aborting: {1}", Format.ToString(srv.EndPoint), ex.Message);
  300. throw;
  301. }
  302. // 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
  303. if (!tieBreakerKey.IsNull)
  304. {
  305. LogLocked(log, "Resending tie-breaker to {0}...", Format.ToString(server.EndPoint));
  306. msg = Message.Create(0, flags, RedisCommand.SET, tieBreakerKey, newMaster);
  307. server.QueueDirectFireAndForget(msg, ResultProcessor.DemandOK);
  308. }
  309. // try and broadcast this everywhere, to catch the maximum audience
  310. if ((options & ReplicationChangeOptions.Broadcast) != 0 && ConfigurationChangedChannel != null
  311. && CommandMap.IsAvailable(RedisCommand.PUBLISH))
  312. {
  313. RedisValue channel = ConfigurationChangedChannel;
  314. foreach (var node in nodes)
  315. {
  316. if (!node.IsConnected) continue;
  317. LogLocked(log, "Broadcasting via {0}...", Format.ToString(node.EndPoint));
  318. msg = Message.Create(-1, flags, RedisCommand.PUBLISH, channel, newMaster);
  319. node.QueueDirectFireAndForget(msg, ResultProcessor.Int64);
  320. }
  321. }
  322. if ((options & ReplicationChangeOptions.EnslaveSubordinates) != 0)
  323. {
  324. foreach (var node in nodes)
  325. {
  326. if (node == server || node.ServerType != ServerType.Standalone) continue;
  327. LogLocked(log, "Enslaving {0}...", Format.ToString(node.EndPoint));
  328. msg = RedisServer.CreateSlaveOfMessage(server.EndPoint, flags);
  329. node.QueueDirectFireAndForget(msg, ResultProcessor.DemandOK);
  330. }
  331. }
  332. // and reconfigure the muxer
  333. LogLocked(log, "Reconfiguring all endpoints...");
  334. if (!ReconfigureAsync(false, true, log, srv.EndPoint, "make master").ObserveErrors().Wait(5000))
  335. {
  336. LogLocked(log, "Verifying the configuration was incomplete; please verify");
  337. }
  338. }
  339. /// <summary>
  340. /// Used internally to synchronize loggine without depending on locking the log instance
  341. /// </summary>
  342. private object LogSyncLock { get { return UniqueId; } } // we know this has strong identity: readonly and unique to us
  343. internal void LogLocked(TextWriter log, string line)
  344. {
  345. lock (LogSyncLock) { log.WriteLine(line); }
  346. }
  347. internal void LogLocked(TextWriter log, string line, object arg)
  348. {
  349. lock (LogSyncLock) { log.WriteLine(line, arg); }
  350. }
  351. internal void LogLocked(TextWriter log, string line, object arg0, object arg1)
  352. {
  353. lock (LogSyncLock) { log.WriteLine(line, arg0, arg1); }
  354. }
  355. internal void LogLocked(TextWriter log, string line, object arg0, object arg1, object arg2)
  356. {
  357. lock (LogSyncLock) { log.WriteLine(line, arg0, arg1, arg2); }
  358. }
  359. internal void LogLocked(TextWriter log, string line, params object[] args)
  360. {
  361. lock (LogSyncLock) { log.WriteLine(line, args); }
  362. }
  363. internal void CheckMessage(Message message)
  364. {
  365. if (!configuration.AllowAdmin && message.IsAdmin)
  366. throw ExceptionFactory.AdminModeNotEnabled(IncludeDetailInExceptions, message.Command, message, null);
  367. CommandMap.AssertAvailable(message.Command);
  368. }
  369. static void WriteNormalizingLineEndings(string source, StreamWriter writer)
  370. {
  371. using (var reader = new StringReader(source))
  372. {
  373. string line;
  374. while ((line = reader.ReadLine()) != null)
  375. writer.WriteLine(line); // normalize line endings
  376. }
  377. }
  378. /// <summary>
  379. /// Raised whenever a physical connection fails
  380. /// </summary>
  381. public event EventHandler<ConnectionFailedEventArgs> ConnectionFailed;
  382. /// <summary>
  383. /// Raised whenever an internal error occurs (this is primarily for debugging)
  384. /// </summary>
  385. public event EventHandler<InternalErrorEventArgs> InternalError;
  386. /// <summary>
  387. /// Raised whenever a physical connection is established
  388. /// </summary>
  389. public event EventHandler<ConnectionFailedEventArgs> ConnectionRestored;
  390. /// <summary>
  391. /// Raised when configuration changes are detected
  392. /// </summary>
  393. public event EventHandler<EndPointEventArgs> ConfigurationChanged;
  394. /// <summary>
  395. /// Raised when nodes are explicitly requested to reconfigure via broadcast;
  396. /// this usually means master/slave changes
  397. /// </summary>
  398. public event EventHandler<EndPointEventArgs> ConfigurationChangedBroadcast;
  399. /// <summary>
  400. /// Gets the timeout associated with the connections
  401. /// </summary>
  402. public int TimeoutMilliseconds
  403. {
  404. get { return timeoutMilliseconds; }
  405. }
  406. /// <summary>
  407. /// Gets all endpoints defined on the server
  408. /// </summary>
  409. /// <returns></returns>
  410. public EndPoint[] GetEndPoints(bool configuredOnly = false)
  411. {
  412. if (configuredOnly) return configuration.EndPoints.ToArray();
  413. return Array.ConvertAll(serverSnapshot, x => x.EndPoint);
  414. }
  415. private readonly int timeoutMilliseconds;
  416. private readonly ConfigurationOptions configuration;
  417. internal bool TryResend(int hashSlot, Message message, EndPoint endpoint, bool isMoved)
  418. {
  419. return serverSelectionStrategy.TryResend(hashSlot, message, endpoint, isMoved);
  420. }
  421. /// <summary>
  422. /// Wait for a given asynchronous operation to complete (or timeout)
  423. /// </summary>
  424. public void Wait(Task task)
  425. {
  426. if (task == null) throw new ArgumentNullException("task");
  427. if (!task.Wait(timeoutMilliseconds)) throw new TimeoutException();
  428. }
  429. /// <summary>
  430. /// Wait for a given asynchronous operation to complete (or timeout)
  431. /// </summary>
  432. public T Wait<T>(Task<T> task)
  433. {
  434. if (task == null) throw new ArgumentNullException("task");
  435. if (!task.Wait(timeoutMilliseconds)) throw new TimeoutException();
  436. return task.Result;
  437. }
  438. /// <summary>
  439. /// Wait for the given asynchronous operations to complete (or timeout)
  440. /// </summary>
  441. public void WaitAll(params Task[] tasks)
  442. {
  443. if (tasks == null) throw new ArgumentNullException("tasks");
  444. if (tasks.Length == 0) return;
  445. if (!Task.WaitAll(tasks, timeoutMilliseconds)) throw new TimeoutException();
  446. }
  447. private bool WaitAllIgnoreErrors(Task[] tasks)
  448. {
  449. return WaitAllIgnoreErrors(tasks, timeoutMilliseconds);
  450. }
  451. private static bool WaitAllIgnoreErrors(Task[] tasks, int timeout)
  452. {
  453. if (tasks == null) throw new ArgumentNullException("tasks");
  454. if (tasks.Length == 0) return true;
  455. var watch = Stopwatch.StartNew();
  456. try
  457. {
  458. // if none error, great
  459. if (Task.WaitAll(tasks, timeout)) return true;
  460. }
  461. catch
  462. { }
  463. // if we get problems, need to give the non-failing ones time to finish
  464. // to be fair and reasonable
  465. for (int i = 0; i < tasks.Length; i++)
  466. {
  467. var task = tasks[i];
  468. if (!task.IsCanceled && !task.IsCompleted && !task.IsFaulted)
  469. {
  470. var remaining = timeout - checked((int)watch.ElapsedMilliseconds);
  471. if (remaining <= 0) return false;
  472. try
  473. {
  474. task.Wait(remaining);
  475. }
  476. catch
  477. { }
  478. }
  479. }
  480. return false;
  481. }
  482. private static async Task<bool> WaitAllIgnoreErrorsAsync(Task[] tasks, int timeoutMilliseconds)
  483. {
  484. if (tasks == null) throw new ArgumentNullException("tasks");
  485. if (tasks.Length == 0) return true;
  486. var watch = Stopwatch.StartNew();
  487. try
  488. {
  489. // if none error, great
  490. #if NET40
  491. var allTasks = TaskEx.WhenAll(tasks).ObserveErrors();
  492. var any = TaskEx.WhenAny(allTasks, TaskEx.Delay(timeoutMilliseconds)).ObserveErrors();
  493. #else
  494. var allTasks = Task.WhenAll(tasks).ObserveErrors();
  495. var any = Task.WhenAny(allTasks, Task.Delay(timeoutMilliseconds)).ObserveErrors();
  496. #endif
  497. return await any.ForAwait() == allTasks;
  498. }
  499. catch
  500. { }
  501. // if we get problems, need to give the non-failing ones time to finish
  502. // to be fair and reasonable
  503. for (int i = 0; i < tasks.Length; i++)
  504. {
  505. var task = tasks[i];
  506. if (!task.IsCanceled && !task.IsCompleted && !task.IsFaulted)
  507. {
  508. var remaining = timeoutMilliseconds - checked((int)watch.ElapsedMilliseconds);
  509. if (remaining <= 0) return false;
  510. try
  511. {
  512. #if NET40
  513. var any = TaskEx.WhenAny(task, TaskEx.Delay(remaining)).ObserveErrors();
  514. #else
  515. var any = Task.WhenAny(task, Task.Delay(remaining)).ObserveErrors();
  516. #endif
  517. await any.ForAwait();
  518. }
  519. catch
  520. { }
  521. }
  522. }
  523. return false;
  524. }
  525. /// <summary>
  526. /// Raised when a hash-slot has been relocated
  527. /// </summary>
  528. public event EventHandler<HashSlotMovedEventArgs> HashSlotMoved;
  529. internal void OnHashSlotMoved(int hashSlot, EndPoint old, EndPoint @new)
  530. {
  531. var handler = HashSlotMoved;
  532. if (handler != null)
  533. {
  534. unprocessableCompletionManager.CompleteSyncOrAsync(
  535. new HashSlotMovedEventArgs(handler, this, hashSlot, old, @new)
  536. );
  537. }
  538. }
  539. /// <summary>
  540. /// Compute the hash-slot of a specified key
  541. /// </summary>
  542. public int HashSlot(RedisKey key)
  543. {
  544. return serverSelectionStrategy.HashSlot(key);
  545. }
  546. internal ServerEndPoint AnyConnected(ServerType serverType, uint startOffset, RedisCommand command, CommandFlags flags)
  547. {
  548. var tmp = serverSnapshot;
  549. int len = tmp.Length;
  550. ServerEndPoint fallback = null;
  551. for (int i = 0; i < len; i++)
  552. {
  553. var server = tmp[(int)(((uint)i + startOffset) % len)];
  554. if (server.ServerType == serverType && server.IsSelectable(command))
  555. {
  556. if (server.IsSlave)
  557. {
  558. switch (flags)
  559. {
  560. case CommandFlags.DemandSlave:
  561. case CommandFlags.PreferSlave:
  562. return server;
  563. case CommandFlags.PreferMaster:
  564. fallback = server;
  565. break;
  566. }
  567. } else
  568. {
  569. switch (flags)
  570. {
  571. case CommandFlags.DemandMaster:
  572. case CommandFlags.PreferMaster:
  573. return server;
  574. case CommandFlags.PreferSlave:
  575. fallback = server;
  576. break;
  577. }
  578. }
  579. }
  580. }
  581. return fallback;
  582. }
  583. volatile bool isDisposed;
  584. internal bool IsDisposed { get { return isDisposed; } }
  585. /// <summary>
  586. /// Create a new ConnectionMultiplexer instance
  587. /// </summary>
  588. public static async Task<ConnectionMultiplexer> ConnectAsync(string configuration, TextWriter log = null)
  589. {
  590. IDisposable killMe = null;
  591. try
  592. {
  593. var muxer = CreateMultiplexer(configuration);
  594. killMe = muxer;
  595. bool configured = await muxer.ReconfigureAsync(true, false, log, null, "connect").ObserveErrors().ForAwait();
  596. if (!configured)
  597. {
  598. throw ExceptionFactory.UnableToConnect(muxer.failureMessage);
  599. }
  600. killMe = null;
  601. return muxer;
  602. } finally
  603. {
  604. if (killMe != null) try { killMe.Dispose(); } catch { }
  605. }
  606. }
  607. /// <summary>
  608. /// Create a new ConnectionMultiplexer instance
  609. /// </summary>
  610. public static async Task<ConnectionMultiplexer> ConnectAsync(ConfigurationOptions configuration, TextWriter log = null)
  611. {
  612. IDisposable killMe = null;
  613. try
  614. {
  615. var muxer = CreateMultiplexer(configuration);
  616. killMe = muxer;
  617. bool configured = await muxer.ReconfigureAsync(true, false, log, null, "connect").ObserveErrors().ForAwait();
  618. if (!configured)
  619. {
  620. throw ExceptionFactory.UnableToConnect(muxer.failureMessage);
  621. }
  622. killMe = null;
  623. return muxer;
  624. } finally
  625. {
  626. if (killMe != null) try { killMe.Dispose(); } catch { }
  627. }
  628. }
  629. static ConnectionMultiplexer CreateMultiplexer(object configuration)
  630. {
  631. if (configuration == null) throw new ArgumentNullException("configuration");
  632. ConfigurationOptions config;
  633. if (configuration is string)
  634. {
  635. config = ConfigurationOptions.Parse((string)configuration);
  636. } else if (configuration is ConfigurationOptions)
  637. {
  638. config = ((ConfigurationOptions)configuration).Clone();
  639. } else
  640. {
  641. throw new ArgumentException("configuration");
  642. }
  643. if (config.EndPoints.Count == 0) throw new ArgumentException("No endpoints specified", "configuration");
  644. config.SetDefaultPorts();
  645. return new ConnectionMultiplexer(config);
  646. }
  647. /// <summary>
  648. /// Create a new ConnectionMultiplexer instance
  649. /// </summary>
  650. public static ConnectionMultiplexer Connect(string configuration, TextWriter log = null)
  651. {
  652. IDisposable killMe = null;
  653. try
  654. {
  655. var muxer = CreateMultiplexer(configuration);
  656. killMe = muxer;
  657. // note that task has timeouts internally, so it might take *just over* the reegular timeout
  658. var task = muxer.ReconfigureAsync(true, false, log, null, "connect");
  659. if (!task.Wait(muxer.SyncConnectTimeout(true)))
  660. {
  661. task.ObserveErrors();
  662. if (muxer.RawConfig.AbortOnConnectFail)
  663. {
  664. throw new TimeoutException();
  665. }
  666. }
  667. if(!task.Result) throw ExceptionFactory.UnableToConnect(muxer.failureMessage);
  668. killMe = null;
  669. return muxer;
  670. }
  671. finally
  672. {
  673. if (killMe != null) try { killMe.Dispose(); } catch { }
  674. }
  675. }
  676. /// <summary>
  677. /// Create a new ConnectionMultiplexer instance
  678. /// </summary>
  679. public static ConnectionMultiplexer Connect(ConfigurationOptions configuration, TextWriter log = null)
  680. {
  681. IDisposable killMe = null;
  682. try
  683. {
  684. var muxer = CreateMultiplexer(configuration);
  685. killMe = muxer;
  686. // note that task has timeouts internally, so it might take *just over* the reegular timeout
  687. var task = muxer.ReconfigureAsync(true, false, log, null, "connect");
  688. if (!task.Wait(muxer.SyncConnectTimeout(true)))
  689. {
  690. task.ObserveErrors();
  691. if (muxer.RawConfig.AbortOnConnectFail)
  692. {
  693. throw new TimeoutException();
  694. }
  695. }
  696. if (!task.Result) throw ExceptionFactory.UnableToConnect(muxer.failureMessage);
  697. killMe = null;
  698. return muxer;
  699. }
  700. finally
  701. {
  702. if (killMe != null) try { killMe.Dispose(); } catch { }
  703. }
  704. }
  705. private string failureMessage;
  706. private readonly Hashtable servers = new Hashtable();
  707. private volatile ServerEndPoint[] serverSnapshot = NilServers;
  708. private static readonly ServerEndPoint[] NilServers = new ServerEndPoint[0];
  709. internal ServerEndPoint GetServerEndPoint(EndPoint endpoint)
  710. {
  711. if (endpoint == null) return null;
  712. var server = (ServerEndPoint)servers[endpoint];
  713. if (server == null)
  714. {
  715. lock (servers)
  716. {
  717. server = (ServerEndPoint)servers[endpoint];
  718. if (server == null)
  719. {
  720. if (isDisposed) throw new ObjectDisposedException(ToString());
  721. server = new ServerEndPoint(this, endpoint);
  722. servers.Add(endpoint, server);
  723. var newSnapshot = serverSnapshot;
  724. Array.Resize(ref newSnapshot, newSnapshot.Length + 1);
  725. newSnapshot[newSnapshot.Length - 1] = server;
  726. serverSnapshot = newSnapshot;
  727. }
  728. }
  729. }
  730. return server;
  731. }
  732. internal readonly CommandMap CommandMap;
  733. private ConnectionMultiplexer(ConfigurationOptions configuration)
  734. {
  735. if (configuration == null) throw new ArgumentNullException("configuration");
  736. IncludeDetailInExceptions = true;
  737. this.configuration = configuration;
  738. var map = this.CommandMap = configuration.CommandMap;
  739. if (!string.IsNullOrWhiteSpace(configuration.Password)) map.AssertAvailable(RedisCommand.AUTH);
  740. if(!map.IsAvailable(RedisCommand.ECHO) && !map.IsAvailable(RedisCommand.PING) && !map.IsAvailable(RedisCommand.TIME))
  741. { // I mean really, give me a CHANCE! I need *something* to check the server is available to me...
  742. // see also: SendTracer (matching logic)
  743. map.AssertAvailable(RedisCommand.EXISTS);
  744. }
  745. PreserveAsyncOrder = true; // safest default
  746. this.timeoutMilliseconds = configuration.SyncTimeout;
  747. OnCreateReaderWriter(configuration);
  748. unprocessableCompletionManager = new CompletionManager(this, "multiplexer");
  749. serverSelectionStrategy = new ServerSelectionStrategy(this);
  750. var configChannel = configuration.ConfigurationChannel;
  751. if (!string.IsNullOrWhiteSpace(configChannel))
  752. {
  753. ConfigurationChangedChannel = Encoding.UTF8.GetBytes(configChannel);
  754. }
  755. lastHeartbeatTicks = Environment.TickCount;
  756. }
  757. partial void OnCreateReaderWriter(ConfigurationOptions configuration);
  758. internal const int MillisecondsPerHeartbeat = 1000;
  759. private static readonly TimerCallback heartbeat = state =>
  760. {
  761. ((ConnectionMultiplexer)state).OnHeartbeat();
  762. };
  763. private void OnHeartbeat()
  764. {
  765. try
  766. {
  767. int now = Environment.TickCount;
  768. Interlocked.Exchange(ref lastHeartbeatTicks, now);
  769. Interlocked.Exchange(ref lastGlobalHeartbeatTicks, now);
  770. Trace("heartbeat");
  771. var tmp = serverSnapshot;
  772. for (int i = 0; i < tmp.Length; i++)
  773. tmp[i].OnHeartbeat();
  774. } catch(Exception ex)
  775. {
  776. OnInternalError(ex);
  777. }
  778. }
  779. private int lastHeartbeatTicks;
  780. private static int lastGlobalHeartbeatTicks = Environment.TickCount;
  781. internal long LastHeartbeatSecondsAgo {
  782. get {
  783. if (pulse == null) return -1;
  784. return unchecked(Environment.TickCount - Thread.VolatileRead(ref lastHeartbeatTicks)) / 1000;
  785. }
  786. }
  787. internal static long LastGlobalHeartbeatSecondsAgo
  788. { get { return unchecked(Environment.TickCount - Thread.VolatileRead(ref lastGlobalHeartbeatTicks)) / 1000; } }
  789. internal CompletionManager UnprocessableCompletionManager { get { return unprocessableCompletionManager; } }
  790. /// <summary>
  791. /// Obtain a pub/sub subscriber connection to the specified server
  792. /// </summary>
  793. public ISubscriber GetSubscriber(object asyncState = null)
  794. {
  795. if (RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("The pub/sub API is not available via twemproxy");
  796. return new RedisSubscriber(this, asyncState);
  797. }
  798. /// <summary>
  799. /// Obtain an interactive connection to a database inside redis
  800. /// </summary>
  801. public IDatabase GetDatabase(int db = 0, object asyncState = null)
  802. {
  803. if (db < 0) throw new ArgumentOutOfRangeException("db");
  804. if (db != 0 && RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("Twemproxy only supports database 0");
  805. return new RedisDatabase(this, db, asyncState);
  806. }
  807. /// <summary>
  808. /// Obtain a configuration API for an individual server
  809. /// </summary>
  810. public IServer GetServer(string host, int port, object asyncState = null)
  811. {
  812. return GetServer(Format.ParseEndPoint(host, port), asyncState);
  813. }
  814. /// <summary>
  815. /// Obtain a configuration API for an individual server
  816. /// </summary>
  817. public IServer GetServer(string hostAndPort, object asyncState = null)
  818. {
  819. return GetServer(Format.TryParseEndPoint(hostAndPort), asyncState);
  820. }
  821. /// <summary>
  822. /// Obtain a configuration API for an individual server
  823. /// </summary>
  824. public IServer GetServer(IPAddress host, int port)
  825. {
  826. return GetServer(new IPEndPoint(host, port));
  827. }
  828. /// <summary>
  829. /// Obtain a configuration API for an individual server
  830. /// </summary>
  831. public IServer GetServer(EndPoint endpoint, object asyncState = null)
  832. {
  833. if (endpoint == null) throw new ArgumentNullException("endpoint");
  834. if (RawConfig.Proxy == Proxy.Twemproxy) throw new NotSupportedException("The server API is not available via twemproxy");
  835. var server = (ServerEndPoint)servers[endpoint];
  836. if (server == null) throw new ArgumentException("The specified endpoint is not defined", "endpoint");
  837. return new RedisServer(this, server, asyncState);
  838. }
  839. [Conditional("VERBOSE")]
  840. internal void Trace(string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null)
  841. {
  842. OnTrace(message, category);
  843. }
  844. [Conditional("VERBOSE")]
  845. internal void Trace(bool condition, string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null)
  846. {
  847. if (condition) OnTrace(message, category);
  848. }
  849. partial void OnTrace(string message, string category);
  850. static partial void OnTraceWithoutContext(string message, string category);
  851. [Conditional("VERBOSE")]
  852. internal static void TraceWithoutContext(string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null)
  853. {
  854. OnTraceWithoutContext(message, category);
  855. }
  856. [Conditional("VERBOSE")]
  857. internal static void TraceWithoutContext(bool condition, string message, [System.Runtime.CompilerServices.CallerMemberName] string category = null)
  858. {
  859. if(condition) OnTraceWithoutContext(message, category);
  860. }
  861. private readonly CompletionManager unprocessableCompletionManager;
  862. /// <summary>
  863. /// The number of operations that have been performed on all connections
  864. /// </summary>
  865. public long OperationCount {
  866. get
  867. {
  868. long total = 0;
  869. var snapshot = serverSnapshot;
  870. for (int i = 0; i < snapshot.Length; i++) total += snapshot[i].OperationCount;
  871. return total;
  872. }
  873. }
  874. string activeConfigCause;
  875. internal void ReconfigureIfNeeded(EndPoint blame, bool fromBroadcast, string cause)
  876. {
  877. if (fromBroadcast)
  878. {
  879. OnConfigurationChangedBroadcast(blame);
  880. }
  881. string activeCause = Interlocked.CompareExchange(ref activeConfigCause, null, null);
  882. if (activeCause == null)
  883. {
  884. bool reconfigureAll = fromBroadcast;
  885. Trace("Configuration change detected; checking nodes", "Configuration");
  886. ReconfigureAsync(false, reconfigureAll, null, blame, cause).ObserveErrors();
  887. } else
  888. {
  889. Trace("Configuration change skipped; already in progress via " + activeCause, "Configuration");
  890. }
  891. }
  892. /// <summary>
  893. /// Reconfigure the current connections based on the existing configuration
  894. /// </summary>
  895. public Task<bool> ConfigureAsync(TextWriter log = null)
  896. {
  897. return ReconfigureAsync(false, true, log, null, "configure").ObserveErrors();
  898. }
  899. /// <summary>
  900. /// Reconfigure the current connections based on the existing configuration
  901. /// </summary>
  902. public bool Configure(TextWriter log = null)
  903. {
  904. // note we expect ReconfigureAsync to internally allow [n] duration,
  905. // so to avoid near misses, here we wait 2*[n]
  906. var task = ReconfigureAsync(false, true, log, null, "configure");
  907. if (!task.Wait(SyncConnectTimeout(false)))
  908. {
  909. task.ObserveErrors();
  910. if (configuration.AbortOnConnectFail)
  911. {
  912. throw new TimeoutException();
  913. }
  914. return false;
  915. }
  916. return task.Result;
  917. }
  918. internal int SyncConnectTimeout(bool forConnect)
  919. {
  920. int retryCount = forConnect ? RawConfig.ConnectRetry : 1;
  921. if (retryCount <= 0) retryCount = 1;
  922. int timeout = configuration.ConnectTimeout;
  923. if (timeout >= int.MaxValue / retryCount) return int.MaxValue;
  924. timeout *= retryCount;
  925. if (timeout >= int.MaxValue - 500) return int.MaxValue;
  926. return timeout + Math.Min(500, timeout);
  927. }
  928. /// <summary>
  929. /// Provides a text overview of the status of all connections
  930. /// </summary>
  931. public string GetStatus()
  932. {
  933. using(var sw = new StringWriter())
  934. {
  935. GetStatus(sw);
  936. return sw.ToString();
  937. }
  938. }
  939. /// <summary>
  940. /// Provides a text overview of the status of all connections
  941. /// </summary>
  942. public void GetStatus(TextWriter log)
  943. {
  944. if (log == null) return;
  945. var tmp = serverSnapshot;
  946. foreach (var server in tmp)
  947. {
  948. LogLocked(log, server.Summary());
  949. LogLocked(log, server.GetCounters().ToString());
  950. LogLocked(log, server.GetProfile());
  951. }
  952. LogLocked(log, "Sync timeouts: {0}; fire and forget: {1}; last heartbeat: {2}s ago",
  953. Interlocked.Read(ref syncTimeouts), Interlocked.Read(ref fireAndForgets), LastHeartbeatSecondsAgo);
  954. }
  955. internal async Task<bool> ReconfigureAsync(bool first, bool reconfigureAll, TextWriter log, EndPoint blame, string cause)
  956. {
  957. if (isDisposed) throw new ObjectDisposedException(ToString());
  958. bool showStats = true;
  959. if (log == null)
  960. {
  961. log = TextWriter.Null;
  962. showStats = false;
  963. }
  964. bool ranThisCall = false;
  965. try
  966. { // note that "activeReconfigs" starts at one; we don't need to set it the first time
  967. ranThisCall = first || Interlocked.CompareExchange(ref activeConfigCause, cause, null) == null;
  968. if (!ranThisCall)
  969. {
  970. LogLocked(log, "Reconfiguration was already in progress");
  971. return false;
  972. }
  973. Trace("Starting reconfiguration...");
  974. Trace(blame != null, "Blaming: " + Format.ToString(blame));
  975. LogLocked(log, Configuration);
  976. LogLocked(log, "");
  977. if (first)
  978. {
  979. if (configuration.ResolveDns && configuration.HasDnsEndPoints())
  980. {
  981. var dns = configuration.ResolveEndPointsAsync(this, log).ObserveErrors();
  982. #if NET40
  983. var any = TaskEx.WhenAny(dns, TaskEx.Delay(timeoutMilliseconds));
  984. #else
  985. var any = Task.WhenAny(dns, Task.Delay(timeoutMilliseconds));
  986. #endif
  987. if ((await any.ForAwait()) != dns)
  988. {
  989. throw new TimeoutException("Timeout resolving endpoints");
  990. }
  991. }
  992. int index = 0;
  993. lock (this.servers)
  994. {
  995. serverSnapshot = new ServerEndPoint[configuration.EndPoints.Count];
  996. foreach (var endpoint in configuration.EndPoints)
  997. {
  998. var server = new ServerEndPoint(this, endpoint);
  999. serverSnapshot[index++] = server;
  1000. this.servers.Add(endpoint, server);
  1001. }
  1002. }
  1003. foreach (var server in serverSnapshot)
  1004. {
  1005. server.Activate(ConnectionType.Interactive);
  1006. if (this.CommandMap.IsAvailable(RedisCommand.SUBSCRIBE))
  1007. {
  1008. server.Activate(ConnectionType.Subscription);
  1009. }
  1010. }
  1011. }
  1012. int attemptsLeft = first ? configuration.ConnectRetry : 1;
  1013. bool healthy = false;
  1014. do
  1015. {
  1016. if (first)
  1017. {
  1018. attemptsLeft--;
  1019. }
  1020. int standaloneCount = 0, clusterCount = 0;
  1021. var endpoints = configuration.EndPoints;
  1022. LogLocked(log, "{0} unique nodes specified", endpoints.Count);
  1023. if (endpoints.Count == 0)
  1024. {
  1025. throw new InvalidOperationException("No nodes to consider");
  1026. }
  1027. const CommandFlags flags = CommandFlags.NoRedirect | CommandFlags.HighPriority;
  1028. var available = new Task<bool>[endpoints.Count];
  1029. var servers = new ServerEndPoint[available.Length];
  1030. bool useTieBreakers = !string.IsNullOrWhiteSpace(configuration.TieBreaker);
  1031. var tieBreakers = useTieBreakers ? new Task<string>[endpoints.Count] : null;
  1032. RedisKey tieBreakerKey = useTieBreakers ? (RedisKey)configuration.TieBreaker : default(RedisKey);
  1033. for (int i = 0; i < available.Length; i++)
  1034. {
  1035. Trace("Testing: " + Format.ToString(endpoints[i]));
  1036. var server = GetServerEndPoint(endpoints[i]);
  1037. //server.ReportNextFailure();
  1038. servers[i] = server;
  1039. if (reconfigureAll && server.IsConnected)
  1040. {
  1041. LogLocked(log, "Refreshing {0}...", Format.ToString(server.EndPoint));
  1042. // note that these will be processed synchronously *BEFORE* the tracer is processed,
  1043. // so we know that the configuration will be up to date if we see the tracer
  1044. server.AutoConfigure(null);
  1045. }
  1046. available[i] = server.SendTracer();
  1047. Message msg;
  1048. if (useTieBreakers)
  1049. {
  1050. LogLocked(log, "Requesting tie-break from {0} > {1}...", Format.ToString(server.EndPoint), configuration.TieBreaker);
  1051. msg = Message.Create(0, flags, RedisCommand.GET, tieBreakerKey);
  1052. msg.SetInternalCall();
  1053. tieBreakers[i] = server.QueueDirectAsync(msg, ResultProcessor.String);
  1054. }
  1055. }
  1056. LogLocked(log, "Allowing endpoints {0} to respond...", TimeSpan.FromMilliseconds(configuration.ConnectTimeout));
  1057. Trace("Allowing endpoints " + TimeSpan.FromMilliseconds(configuration.ConnectTimeout) + " to respond...");
  1058. await WaitAllIgnoreErrorsAsync(available, configuration.ConnectTimeout).ForAwait();
  1059. List<ServerEndPoint> masters = new List<ServerEndPoint>(available.Length);
  1060. for (int i = 0; i < available.Length; i++)
  1061. {
  1062. var task = available[i];
  1063. Trace(Format.ToString(endpoints[i]) + ": " + task.Status);
  1064. if (task.IsFaulted)
  1065. {
  1066. servers[i].SetUnselectable(UnselectableFlags.DidNotRespond);
  1067. var aex = task.Exception;
  1068. foreach (var ex in aex.InnerExceptions)
  1069. {
  1070. LogLocked(log, "{0} faulted: {1}", Format.ToString(endpoints[i]), ex.Message);
  1071. failureMessage = ex.Message;
  1072. }
  1073. }
  1074. else if (task.IsCanceled)
  1075. {
  1076. servers[i].SetUnselectable(UnselectableFlags.DidNotRespond);
  1077. LogLocked(log, "{0} was canceled", Format.ToString(endpoints[i]));
  1078. }
  1079. else if (task.IsCompleted)
  1080. {
  1081. var server = servers[i];
  1082. if (task.Result)
  1083. {
  1084. servers[i].ClearUnselectable(UnselectableFlags.DidNotRespond);
  1085. LogLocked(log, "{0} returned with success", Format.ToString(endpoints[i]));
  1086. switch (server.ServerType)
  1087. {
  1088. case ServerType.Twemproxy:
  1089. case ServerType.Standalone:
  1090. servers[i].ClearUnselectable(UnselectableFlags.ServerType);
  1091. standaloneCount++;
  1092. if (server.IsSlave)
  1093. {
  1094. servers[i].ClearUnselectable(UnselectableFlags.RedundantMaster);
  1095. }
  1096. else
  1097. {
  1098. masters.Add(server);
  1099. }
  1100. break;
  1101. case ServerType.Cluster:
  1102. servers[i].ClearUnselectable(UnselectableFlags.ServerType);
  1103. clusterCount++;
  1104. if (server.IsSlave)
  1105. {
  1106. servers[i].ClearUnselectable(UnselectableFlags.RedundantMaster);
  1107. }
  1108. else
  1109. {
  1110. masters.Add(server);
  1111. }
  1112. break;
  1113. default:
  1114. servers[i].SetUnselectable(UnselectableFlags.ServerType);
  1115. break;
  1116. }
  1117. }
  1118. else
  1119. {
  1120. servers[i].SetUnselectable(UnselectableFlags.DidNotRespond);
  1121. LogLocked(log, "{0} returned, but incorrectly", Format.ToString(endpoints[i]));
  1122. }
  1123. }
  1124. else
  1125. {
  1126. servers[i].SetUnselectable(UnselectableFlags.DidNotRespond);
  1127. LogLocked(log, "{0} did not respond", Format.ToString(endpoints[i]));
  1128. }
  1129. }
  1130. if (clusterCount == 0)
  1131. {
  1132. this.serverSelectionStrategy.ServerType = RawConfig.Proxy == Proxy.Twemproxy ? ServerType.Twemproxy : ServerType.Standalone;
  1133. var preferred = await NominatePreferredMaster(log, servers, useTieBreakers, tieBreakers, masters).ObserveErrors().ForAwait();
  1134. foreach (var master in masters)
  1135. {
  1136. if (master == preferred)
  1137. {
  1138. master.ClearUnselectable(UnselectableFlags.RedundantMaster);
  1139. }
  1140. else
  1141. {
  1142. master.SetUnselectable(UnselectableFlags.RedundantMaster);
  1143. }
  1144. }
  1145. }
  1146. else
  1147. {
  1148. serverSelectionStrategy.ServerType = ServerType.Cluster;
  1149. long coveredSlots = serverSelectionStrategy.CountCoveredSlots();
  1150. LogLocked(log, "Cluster: {0} of {1} slots covered",
  1151. coveredSlots, serverSelectionStrategy.TotalSlots);
  1152. }
  1153. if (!first)
  1154. {
  1155. long subscriptionChanges = ValidateSubscriptions();
  1156. if (subscriptionChanges == 0)
  1157. {
  1158. LogLocked(log, "No subscription changes necessary");
  1159. }
  1160. else
  1161. {
  1162. LogLocked(log, "Subscriptions reconfigured: {0}", subscriptionChanges);
  1163. }
  1164. }
  1165. if (showStats)
  1166. {
  1167. GetStatus(log);
  1168. }
  1169. string stormLog = GetStormLog();
  1170. if (!string.IsNullOrWhiteSpace(stormLog))
  1171. {
  1172. LogLocked(log, "");
  1173. LogLocked(log, stormLog);
  1174. }
  1175. healthy = standaloneCount != 0 || clusterCount != 0;
  1176. if (first && !healthy && attemptsLeft > 0)
  1177. {
  1178. LogLocked(log, "resetting failing connections to retry...");
  1179. ResetAllNonConnected();
  1180. LogLocked(log, "retrying; attempts left: " + attemptsLeft + "...");
  1181. }
  1182. //WTF("?: " + attempts);
  1183. } while (first && !healthy && attemptsLeft > 0);
  1184. if(first && configuration.AbortOnConnectFail && !healthy)
  1185. {
  1186. return false;
  1187. }
  1188. if (first)
  1189. {
  1190. LogLocked(log, "Starting heartbeat...");
  1191. pulse = new Timer(heartbeat, this, MillisecondsPerHeartbeat, MillisecondsPerHeartbeat);
  1192. }
  1193. return true;
  1194. } catch (Exception ex)
  1195. {
  1196. Trace(ex.Message);
  1197. throw;
  1198. }
  1199. finally
  1200. {
  1201. Trace("Exiting reconfiguration...");
  1202. OnTraceLog(log);
  1203. if (ranThisCall) Interlocked.Exchange(ref activeConfigCause, null);
  1204. if (!first) OnConfigurationChanged(blame);
  1205. Trace("Reconfiguration exited");
  1206. }
  1207. }
  1208. private void ResetAllNonConnected()
  1209. {
  1210. var snapshot = serverSnapshot;
  1211. foreach(var server in snapshot)
  1212. {
  1213. server.ResetNonConnected();
  1214. }
  1215. }
  1216. partial void OnTraceLog(TextWriter log, [System.Runtime.CompilerServices.CallerMemberName] string caller = null);
  1217. private async Task<ServerEndPoint> NominatePreferredMaster(TextWriter log, ServerEndPoint[] servers, bool useTieBreakers, Task<string>[] tieBreakers, List<ServerEndPoint> masters)
  1218. {
  1219. Dictionary<string, int> uniques = null;
  1220. if (useTieBreakers)
  1221. { // count the votes
  1222. uniques = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
  1223. await WaitAllIgnoreErrorsAsync(tieBreakers, 50).ForAwait();
  1224. for (int i = 0; i < tieBreakers.Length; i++)
  1225. {
  1226. var ep = servers[i].EndPoint;
  1227. var status = tieBreakers[i].Status;
  1228. switch (status)
  1229. {
  1230. case TaskStatus.RanToCompletion:
  1231. string s = tieBreakers[i].Result;
  1232. if (string.IsNullOrWhiteSpace(s))
  1233. {
  1234. LogLocked(log, "{0} had no tiebreaker set", Format.ToString(ep));
  1235. }
  1236. else
  1237. {
  1238. LogLocked(log, "{0} nominates: {1}", Format.ToString(ep), s);
  1239. int count;
  1240. if (!uniques.TryGetValue(s, out count)) count = 0;
  1241. uniques[s] = count + 1;
  1242. }
  1243. break;
  1244. case TaskStatus.Faulted:
  1245. LogLocked(log, "{0} failed to nominate ({1})", Format.ToString(ep), status);
  1246. foreach (var ex in tieBreakers[i].Exception.InnerExceptions)
  1247. {
  1248. if (ex.Message.StartsWith("MOVED ") || ex.Message.StartsWith("ASK ")) continue;
  1249. LogLocked(log, "> {0}", ex.Message);
  1250. }
  1251. break;
  1252. default:
  1253. LogLocked(log, "{0} failed to nominate ({1})", Format.ToString(ep), status);
  1254. break;
  1255. }
  1256. }
  1257. }
  1258. switch (masters.Count)
  1259. {
  1260. case 0:
  1261. LogLocked(log, "No masters detected");
  1262. return null;
  1263. case 1:
  1264. LogLocked(log, "Single master detected: " + Format.ToString(masters[0].EndPoint));
  1265. return masters[0];
  1266. default:
  1267. LogLocked(log, "Multiple masters detected...");
  1268. if (useTieBreakers && uniques != null)
  1269. {
  1270. switch (uniques.Count)
  1271. {
  1272. case 0:
  1273. LogLocked(log, "nobody nominated a tie-breaker");
  1274. break;
  1275. case 1:
  1276. string unanimous = uniques.Keys.Single();
  1277. LogLocked(log, "tie-break is unanimous at {0}", unanimous);
  1278. var found = SelectServerByElection(servers, unanimous, log);
  1279. if (found != null)
  1280. {
  1281. LogLocked(log, "Elected: {0}", Format.ToString(found.EndPoint));
  1282. return found;
  1283. }
  1284. break;
  1285. default:
  1286. LogLocked(log, "tie-break is contested:");
  1287. ServerEndPoint highest = null;
  1288. bool arbitrary = false;
  1289. foreach (var pair in uniques.OrderByDescending(x => x.Value))
  1290. {
  1291. LogLocked(log, "{0} has {1} votes", pair.Key, pair.Value);
  1292. if (highest == null)
  1293. {
  1294. highest = SelectServerByElection(servers, pair.Key, log);
  1295. if (highest != null)
  1296. {
  1297. // any more with this vote? if so: arbitrary
  1298. arbitrary = uniques.Where(x => x.Value == pair.Value).Skip(1).Any();
  1299. }
  1300. }
  1301. }
  1302. if (highest != null)
  1303. {
  1304. if (arbitrary)
  1305. {
  1306. LogLocked(log, "Choosing master arbitrarily: {0}", Format.ToString(highest.EndPoint));
  1307. }
  1308. else
  1309. {
  1310. LogLocked(log, "Elected: {0}", Format.ToString(highest.EndPoint));
  1311. }
  1312. return highest;
  1313. }
  1314. break;
  1315. }
  1316. }
  1317. break;
  1318. }
  1319. LogLocked(log, "Choosing master arbitrarily: {0}", Format.ToString(masters[0].EndPoint));
  1320. return masters[0];
  1321. }
  1322. private ServerEndPoint SelectServerByElection(ServerEndPoint[] servers, string endpoint, TextWriter log)
  1323. {
  1324. if (servers == null || string.IsNullOrWhiteSpace(endpoint)) return null;
  1325. for (int i = 0; i < servers.Length; i++)
  1326. {
  1327. if (string.Equals(Format.ToString(servers[i].EndPoint), endpoint, StringComparison.OrdinalIgnoreCase))
  1328. return servers[i];
  1329. }
  1330. LogLocked(log, "...but we couldn't find that");
  1331. return null;
  1332. }
  1333. internal void UpdateClusterRange(ClusterConfiguration configuration)
  1334. {
  1335. if (configuration == null) return;
  1336. foreach (var node in configuration.Nodes)
  1337. {
  1338. if (node.IsSlave || node.Slots.Count == 0) continue;
  1339. foreach (var slot in node.Slots)
  1340. {
  1341. var server = GetServerEndPoint(node.EndPoint);
  1342. if (server != null) serverSelectionStrategy.UpdateClusterRange(slot.From, slot.To, server);
  1343. }
  1344. }
  1345. }
  1346. private Timer pulse;
  1347. private readonly ServerSelectionStrategy serverSelectionStrategy;
  1348. internal ServerEndPoint SelectServer(Message message)
  1349. {
  1350. if (message == null) return null;
  1351. return serverSelectionStrategy.Select(message);
  1352. }
  1353. internal ServerEndPoint SelectServer(int db, RedisCommand command, CommandFlags flags, RedisKey key)
  1354. {
  1355. return serverSelectionStrategy.Select(db, command, key, flags);
  1356. }
  1357. private bool TryPushMessageToBridge<T>(Message message, ResultProcessor<T> processor, ResultBox<T> resultBox, ref ServerEndPoint server)
  1358. {
  1359. message.SetSource(processor, resultBox);
  1360. if (server == null)
  1361. { // infer a server automatically
  1362. server = SelectServer(message);
  1363. }
  1364. else // a server was specified; do we trust their choice, though?
  1365. {
  1366. if (message.IsMasterOnly() && server.IsSlave)
  1367. {
  1368. throw ExceptionFactory.MasterOnly(IncludeDetailInExceptions, message.Command, message, server);
  1369. }
  1370. switch(server.ServerType)
  1371. {
  1372. case ServerType.Cluster:
  1373. case ServerType.Twemproxy: // strictly speaking twemproxy uses a different hashing algo, but the hash-tag behavior is
  1374. // the same, so this does a pretty good job of spotting illegal commands before sending them
  1375. if (message.GetHashSlot(ServerSelectionStrategy) == ServerSelectionStrategy.MultipleSlots)
  1376. {
  1377. throw ExceptionFactory.MultiSlot(IncludeDetailInExceptions, message);
  1378. }
  1379. break;
  1380. }
  1381. if (!server.IsConnected)
  1382. {
  1383. // well, that's no use!
  1384. server = null;
  1385. }
  1386. }
  1387. if (server != null)
  1388. {
  1389. if (message.Db >= 0)
  1390. {
  1391. int availableDatabases = server.Databases;
  1392. if (availableDatabases > 0 && message.Db >= availableDatabases) throw ExceptionFactory.DatabaseOutfRange(
  1393. IncludeDetailInExceptions, message.Db, message, server);
  1394. }
  1395. Trace("Queueing on server: " + message);
  1396. if (server.TryEnqueue(message)) return true;
  1397. }
  1398. Trace("No server or server unavailable - aborting: " + message);
  1399. return false;
  1400. }
  1401. /// <summary>
  1402. /// See Object.ToString()
  1403. /// </summary>
  1404. public override string ToString()
  1405. {
  1406. string s = ClientName;
  1407. if (string.IsNullOrWhiteSpace(s)) s = GetType().Name;
  1408. return s;
  1409. }
  1410. internal readonly byte[] ConfigurationChangedChannel; // this gets accessed for every received event; let's make sure we can process it "raw"
  1411. internal readonly byte[] UniqueId = Guid.NewGuid().ToByteArray(); // unique identifier used when tracing
  1412. /// <summary>
  1413. /// Gets or sets whether asynchronous operations should be invoked in a way that guarantees their original delivery order
  1414. /// </summary>
  1415. public bool PreserveAsyncOrder { get; set; }
  1416. /// <summary>
  1417. /// Indicates whether any servers are connected
  1418. /// </summary>
  1419. public bool IsConnected
  1420. {
  1421. get
  1422. {
  1423. var tmp = serverSnapshot;
  1424. for (int i = 0; i < tmp.Length; i++)
  1425. if (tmp[i].IsConnected) return true;
  1426. return false;
  1427. }
  1428. }
  1429. internal ConfigurationOptions RawConfig { get { return configuration; } }
  1430. internal ServerSelectionStrategy ServerSelectionStrategy { get { return serverSelectionStrategy; } }
  1431. /// <summary>
  1432. /// Close all connections and release all resources associated with this object
  1433. /// </summary>
  1434. public void Close(bool allowCommandsToComplete = true)
  1435. {
  1436. isDisposed = true;
  1437. using (var tmp = pulse)
  1438. {
  1439. pulse = null;
  1440. }
  1441. if (allowCommandsToComplete)
  1442. {
  1443. var quits = QuitAllServers();
  1444. WaitAllIgnoreErrors(quits);
  1445. }
  1446. DisposeAndClearServers();
  1447. OnCloseReaderWriter();
  1448. }
  1449. partial void OnCloseReaderWriter();
  1450. private void DisposeAndClearServers()
  1451. {
  1452. lock (servers)
  1453. {
  1454. var iter = servers.GetEnumerator();
  1455. while (iter.MoveNext())
  1456. {
  1457. var server = (ServerEndPoint)iter.Value;
  1458. server.Dispose();
  1459. }
  1460. servers.Clear();
  1461. }
  1462. }
  1463. private Task[] QuitAllServers()
  1464. {
  1465. Task[] quits = new Task[servers.Count];
  1466. lock (servers)
  1467. {
  1468. var iter = servers.GetEnumerator();
  1469. int index = 0;
  1470. while (iter.MoveNext())
  1471. {
  1472. var server = (ServerEndPoint)iter.Value;
  1473. quits[index++] = server.Close();
  1474. }
  1475. }
  1476. return quits;
  1477. }
  1478. /// <summary>
  1479. /// Close all connections and release all resources associated with this object
  1480. /// </summary>
  1481. public async Task CloseAsync(bool allowCommandsToComplete = true)
  1482. {
  1483. isDisposed = true;
  1484. using (var tmp = pulse)
  1485. {
  1486. pulse = null;
  1487. }
  1488. if (allowCommandsToComplete)
  1489. {
  1490. var quits = QuitAllServers();
  1491. await WaitAllIgnoreErrorsAsync(quits, configuration.SyncTimeout).ForAwait();
  1492. }
  1493. DisposeAndClearServers();
  1494. }
  1495. /// <summary>
  1496. /// Release all resources associated with this object
  1497. /// </summary>
  1498. public void Dispose()
  1499. {
  1500. Close(!isDisposed);
  1501. }
  1502. internal Task<T> ExecuteAsyncImpl<T>(Message message, ResultProcessor<T> processor, object state, ServerEndPoint server)
  1503. {
  1504. if (isDisposed) throw new ObjectDisposedException(ToString());
  1505. if (message == null)
  1506. {
  1507. return CompletedTask<T>.Default(state);
  1508. }
  1509. if (message.IsFireAndForget)
  1510. {
  1511. TryPushMessageToBridge(message, processor, null, ref server);
  1512. return CompletedTask<T>.Default(null); // F+F explicitly does not get async-state
  1513. }
  1514. else
  1515. {
  1516. var tcs = TaskSource.CreateDenyExecSync<T>(state);
  1517. var source = ResultBox<T>.Get(tcs);
  1518. if (!TryPushMessageToBridge(message, processor, source, ref server))
  1519. {
  1520. ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, message.Command, message, server));
  1521. }
  1522. return tcs.Task;
  1523. }
  1524. }
  1525. internal static void ThrowFailed<T>(TaskCompletionSource<T> source, Exception unthrownException)
  1526. {
  1527. try
  1528. {
  1529. throw unthrownException;
  1530. } catch (Exception ex)
  1531. {
  1532. source.TrySetException(ex);
  1533. GC.KeepAlive(source.Task.Exception);
  1534. GC.SuppressFinalize(source.Task);
  1535. }
  1536. }
  1537. internal T ExecuteSyncImpl<T>(Message message, ResultProcessor<T> processor, ServerEndPoint server)
  1538. {
  1539. if (isDisposed) throw new ObjectDisposedException(ToString());
  1540. if (message == null) // fire-and forget could involve a no-op, represented by null - for example Increment by 0
  1541. {
  1542. return default(T);
  1543. }
  1544. if (message.IsFireAndForget)
  1545. {
  1546. TryPushMessageToBridge(message, processor, null, ref server);
  1547. Interlocked.Increment(ref fireAndForgets);
  1548. return default(T);
  1549. }
  1550. else
  1551. {
  1552. var source = ResultBox<T>.Get(null);
  1553. lock (source)
  1554. {
  1555. if (!TryPushMessageToBridge(message, processor, source, ref server))
  1556. {
  1557. throw ExceptionFactory.NoConnectionAvailable(IncludeDetailInExceptions, message.Command, message, server);
  1558. }
  1559. if (Monitor.Wait(source, timeoutMilliseconds))
  1560. {
  1561. Trace("Timeley response to " + message.ToString());
  1562. }
  1563. else
  1564. {
  1565. Trace("Timeout performing " + message.ToString());
  1566. Interlocked.Increment(ref syncTimeouts);
  1567. string errMessage;
  1568. if (server == null || !IncludeDetailInExceptions)
  1569. {
  1570. errMessage = "Timeout performing " + message.Command.ToString();
  1571. }
  1572. else
  1573. {
  1574. int inst, qu, qs, qc, wr, wq, @in, ar;
  1575. int queue = server.GetOutstandingCount(message.Command, out inst, out qu, out qs, out qc, out wr, out wq, out @in, out ar);
  1576. var sb = new StringBuilder("Timeout performing ").Append(message.CommandAndKey)
  1577. .Append(", inst: ").Append(inst)
  1578. .Append(", queue: ").Append(queue).Append(", qu=").Append(qu)
  1579. .Append(", qs=").Append(qs).Append(", qc=").Append(qc)
  1580. .Append(", wr=").Append(wr).Append("/").Append(wq)
  1581. .Append(", in=").Append(@in).Append("/").Append(ar);
  1582. errMessage = sb.ToString();
  1583. if (stormLogThreshold >= 0 && queue >= stormLogThreshold && Interlocked.CompareExchange(ref haveStormLog, 1, 0) == 0)
  1584. {
  1585. var log = server.GetStormLog(message.Command);
  1586. if (string.IsNullOrWhiteSpace(log)) Interlocked.Exchange(ref haveStormLog, 0);
  1587. else Interlocked.Exchange(ref stormLogSnapshot, log);
  1588. }
  1589. }
  1590. throw ExceptionFactory.Timeout(IncludeDetailInExceptions, errMessage, message, server);
  1591. // very important not to return "source" to the pool here
  1592. }
  1593. }
  1594. // snapshot these so that we can recycle the box
  1595. Exception ex;
  1596. T val;
  1597. ResultBox<T>.UnwrapAndRecycle(source, out val, out ex); // now that we aren't locking it...
  1598. if (ex != null) throw ex;
  1599. Trace(message + " received " + val);
  1600. return val;
  1601. }
  1602. }
  1603. /// <summary>
  1604. /// Should exceptions include identifiable details? (key names, additional .Data annotations)
  1605. /// </summary>
  1606. public bool IncludeDetailInExceptions { get; set; }
  1607. int haveStormLog = 0, stormLogThreshold = 15;
  1608. string stormLogSnapshot;
  1609. /// <summary>
  1610. /// Limit at which to start recording unusual busy patterns (only one log will be retained at a time;
  1611. /// set to a negative value to disable this feature)
  1612. /// </summary>
  1613. public int StormLogThreshold { get { return stormLogThreshold; } set { stormLogThreshold = value; } }
  1614. /// <summary>
  1615. /// Obtains the log of unusual busy patterns
  1616. /// </summary>
  1617. public string GetStormLog()
  1618. {
  1619. var result = Interlocked.CompareExchange(ref stormLogSnapshot, null, null);
  1620. return result;
  1621. }
  1622. /// <summary>
  1623. /// Resets the log of unusual busy patterns
  1624. /// </summary>
  1625. public void ResetStormLog()
  1626. {
  1627. Interlocked.Exchange(ref stormLogSnapshot, null);
  1628. Interlocked.Exchange(ref haveStormLog, 0);
  1629. }
  1630. private long syncTimeouts, fireAndForgets;
  1631. /// <summary>
  1632. /// Request all compatible clients to reconfigure or reconnect
  1633. /// </summary>
  1634. /// <returns>The number of instances known to have received the message (however, the actual number can be higher)</returns>
  1635. public long PublishReconfigure(CommandFlags flags = CommandFlags.None)
  1636. {
  1637. byte[] channel = ConfigurationChangedChannel;
  1638. if (channel == null) return 0;
  1639. return GetSubscriber().Publish(channel, RedisLiterals.Wildcard, flags);
  1640. }
  1641. /// <summary>
  1642. /// Request all compatible clients to reconfigure or reconnect
  1643. /// </summary>
  1644. /// <returns>The number of instances known to have received the message (however, the actual number can be higher)</returns>
  1645. public Task<long> PublishReconfigureAsync(CommandFlags flags = CommandFlags.None)
  1646. {
  1647. byte[] channel = ConfigurationChangedChannel;
  1648. if (channel == null) return CompletedTask<long>.Default(null);
  1649. return GetSubscriber().PublishAsync(channel, RedisLiterals.Wildcard, flags);
  1650. }
  1651. }
  1652. }