public IEnumerable <TResult> Read <TResult>(Func <RedisResult, TResult> selector) { var pending = ReadNext(0, false); // need to wait for the first one connection.Wait(pending); do { var current = pending.Result; long cursor = current.ValueItems[0].ValueInt64; var rows = current.ValueItems[1].ValueItems; // kick off the next immediately, but don't wait for it yet pending = ReadNext(cursor, true); // now we can iterate the rows for (int i = 0; i < rows.Length; i++) { yield return(selector(rows[i])); } // wait for the next, if any if (pending != null) { connection.Wait(pending); } } while (pending != null); }
public RedisActivityMonitorTests() { redis = new RedisConnection("localhost", allowAdmin: true); sut = new RedisActivityMonitor(redis); redis.Wait(redis.Open()); redis.Wait(redis.Server.FlushDb(0)); }
public void CanNotOpenNonsenseConnection_IP() { using (var conn = new RedisConnection(Config.LocalHost, 6500)) { conn.Wait(conn.Open()); } }
public RedisConnection GetOpen() { lock (syncConnection) { if (_Connection != null && !(_Connection.State == RedisConnectionBase.ConnectionState.Closed || _Connection.State == RedisConnectionBase.ConnectionState.Closing)) { return _Connection; } if (_Connection == null || (_Connection.State == RedisConnectionBase.ConnectionState.Closed || _Connection.State == RedisConnectionBase.ConnectionState.Closing)) { try { RoqueTrace.Source.Trace(TraceEventType.Information, "[REDIS] connecting to {0}:{1}", _Host, _Port); _Connection = new RedisConnection(_Host, _Port, _Timeout); var openAsync = _Connection.Open(); _Connection.Wait(openAsync); RoqueTrace.Source.Trace(TraceEventType.Information, "[REDIS] connected"); } catch (Exception ex) { RoqueTrace.Source.Trace(TraceEventType.Error, "[REDIS] error connecting to {0}:{1}, {2}", _Host, _Port, ex.Message, ex); throw; } } return _Connection; } }
public void CanNotOpenNonsenseConnection() { using (var conn = new RedisConnection("127.0.0.1", 6500)) { conn.Wait(conn.Open()); } }
public void CanNotOpenNonsenseConnection_DNS() { using (var conn = new RedisConnection("doesnot.exist.ds.aasd981230d.com", 6500)) { conn.Wait(conn.Open()); } }
public TestPackage(RedisConnection conn) { Redis = new RedisBooksleeveConnector(conn); var redisFS = new Resque.FailureService(new RedisBackendFactory(Redis)); UnderTest = new Resque.Resque(new JobCreator(), redisFS, Redis); conn.Wait(conn.Keys.Remove(0, Redis.KeyInNamespace("queue:testQueue"))); }
/// <summary> /// Prompt all clients to reconnect. /// </summary> public static void BroadcastReconnectMessage(RedisConnection connection) { if (connection == null) { throw new ArgumentNullException("connection"); } connection.Wait(connection.Publish(RedisMasterChangedChannel, "*")); }
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters( GlobalFilters.Filters ); RegisterRoutes( RouteTable.Routes ); var redis = new RedisConnection( "localhost" ); redis.Wait( redis.Open() ); Application[ "RedisConnection" ] = redis; }
public void should_create_queue_if_it_does_not_exist() { var testQueue = "testQueue"; using (var conn = new RedisConnection("localhost")) { conn.Open(); var package = new TestPackage(conn); package.UnderTest.Push(testQueue, "Testing"); Assert.IsTrue(conn.Wait(conn.Keys.Exists(0, package.Redis.KeyInNamespace("queue:"+testQueue)))); } }
public List<string> Get(string prefix) { List<string> ret = new List<string>(); RedisConnection connection = new RedisConnection( host: DemoSettings.CustomerRedisCache.Url, password: DemoSettings.CustomerRedisCache.Password); connection.Open(); var list = connection.Wait(connection.Keys.Find(0, "cust:" + prefix.Replace(' ', ':') + "*")); for (int i = 0; i < Math.Min(5, list.Length); i++) { ret.Add(list[i].Substring(5).Replace(':', ' ')); } connection.Close(false); return ret; }
private static RedisConnection GetOpenConnection() { var connectionString = ConfigurationManager.AppSettings["REDISTOGO_URL"]; var uri = new Uri(connectionString); var password = uri.UserInfo.Split(':').LastOrDefault(); var conn = new RedisConnection(uri.Host, uri.Port, password: password, allowAdmin: false, syncTimeout: DefaultTimeout, ioTimeout: DefaultTimeout); conn.Error += (sender, e) => Logger.ErrorException(e.Cause, e.Exception); conn.Wait(conn.Open()); return conn; }
public List<string> Get(string prefix, string category) { List<string> ret = new List<string>(); RedisConnection connection = new RedisConnection( host: DemoSettings.ProductsRedisCache.Url, password: DemoSettings.ProductsRedisCache.Password); connection.Open(); var list = connection.Wait(connection.Keys.Find(0, "prod:" + category + ":" + prefix.Replace(' ', ':') + "*")); for (int i = 0; i < Math.Min(5, list.Length); i++) { string s = list[i].Substring(5); s = s.Substring(s.IndexOf(':') + 1); ret.Add(s.Replace(':', ' ')); } connection.Close(false); return ret; }
public void should_add_item_to_queue() { var testQueue = "testQueue"; var item = new QueuedItem() { @class = "Testing" }; using (var conn = new RedisConnection("localhost")) { conn.Open(); var package = new TestPackage(conn); package.UnderTest.Push(testQueue, item.@class, item.args); var test = conn.Wait(conn.Lists.RemoveLastString(0, package.Redis.KeyInNamespace("queue:" + testQueue))); Assert.AreEqual(item.ToJson(), test); } }
public static void Main(string[] args) { var time = new DateTime(2013, 11, 11, 10, 30, 0); var redis = new RedisConnection("localhost"); redis.Wait(redis.Open()); var activity = new RedisActivityMonitor(redis); // Normal activity. activity.Beacon("documents:1", time.AddSeconds(-15), 1, "John"); activity.Beacon("documents:1", time, 2, "Sue"); activity.Beacon("documents:1", time.AddSeconds(5), 3, "Mary"); PrintAll(activity, "documents:1", time.AddSeconds(23)); // Ignore duplicates. activity.Beacon("documents:2", time, 1, "John"); activity.Beacon("documents:2", time.AddSeconds(5), 1, "John"); PrintAll(activity, "documents:2", time.AddSeconds(7)); redis.Close(abort: true); Console.Read(); }
private static RedisConnection GetOldStyleConnection(string host, int port, bool open = true, bool allowAdmin = false, bool waitForOpen = false, int syncTimeout = 5000, int ioTimeout = 5000) { var conn = new RedisConnection(host, port, syncTimeout: syncTimeout, ioTimeout: ioTimeout, allowAdmin: allowAdmin); conn.Error += (s, args) => { Trace.WriteLine(args.Exception.Message, args.Cause); }; if (open) { var openAsync = conn.Open(); if (waitForOpen) conn.Wait(openAsync); } return conn; }
private void TestSubscriberNameOnRemote(bool setName) { string id = Config.CreateUniqueName(); using (var pub = new RedisConnection(Config.RemoteHost, allowAdmin: true)) using (var sub = new RedisSubscriberConnection(Config.RemoteHost)) { List<string> errors = new List<string>(); EventHandler<BookSleeve.ErrorEventArgs> errorHandler = (sender, args) => { lock (errors) errors.Add(args.Exception.Message); }; pub.Error += errorHandler; sub.Error += errorHandler; if (setName) { pub.Name = "pub_" + id; sub.Name = "sub_" + id; } int count = 0; var subscribe = sub.Subscribe("foo"+id, (key,payload) => Interlocked.Increment(ref count)); Task pOpen = pub.Open(), sOpen = sub.Open(); pub.WaitAll(pOpen, sOpen, subscribe); Assert.AreEqual(0, Interlocked.CompareExchange(ref count, 0, 0), "init message count"); pub.Wait(pub.Publish("foo" + id, "hello")); PubSub.AllowReasonableTimeToPublishAndProcess(); var clients = setName ? pub.Wait(pub.Server.ListClients()) : null; Assert.AreEqual(1, Interlocked.CompareExchange(ref count, 0, 0), "got message"); lock (errors) { foreach (var error in errors) { Console.WriteLine(error); } Assert.AreEqual(0, errors.Count, "zero errors"); } if (setName) { Assert.AreEqual(1, clients.Count(x => x.Name == pub.Name), "pub has name"); Assert.AreEqual(1, clients.Count(x => x.Name == sub.Name), "sub has name"); } } }
internal static RedisConnection GetConnection() { var result = new RedisConnection("127.0.0.1"); result.Error += (s, e) => Trace.WriteLine(e.Exception.Message, e.Cause); result.Wait(result.Open()); return result; }
internal static RedisConnection GetUnsecuredConnection(bool open = true, bool allowAdmin = false, bool waitForOpen = false) { var conn = new RedisConnection(host, unsecuredPort, syncTimeout: 5000, ioTimeout: 5000, allowAdmin: allowAdmin); conn.Error += (s, args) => { Trace.WriteLine(args.Exception.Message, args.Cause); }; if (open) { var openAsync = conn.Open(); if (waitForOpen) conn.Wait(openAsync); } return conn; }
protected RedisConnection GetOpenConnection() { lock (syncConnection) { if (_Connection != null && !(_Connection.State == RedisConnectionBase.ConnectionState.Closed || _Connection.State == RedisConnectionBase.ConnectionState.Closing)) { return _Connection; } if (_Connection == null || (_Connection.State == RedisConnectionBase.ConnectionState.Closed || _Connection.State == RedisConnectionBase.ConnectionState.Closing)) { string host = null; int port = 0; try { if (!Settings.TryGet("host", out host)) { throw new Exception("Redis host is required"); } port = Settings.Get("port", 6379); EnqueueTimeoutSeconds = Settings.Get("enqueueTimeoutSeconds", 5); _Connection = new RedisConnection(host, port); var openAsync = _Connection.Open(); _Connection.Wait(openAsync); if (Roque.Core.RoqueTrace.Switch.TraceInfo) { Trace.TraceInformation(string.Format("[REDIS] connected to {0}:{1}", host, port)); } } catch (Exception ex) { if (Roque.Core.RoqueTrace.Switch.TraceError) { Trace.TraceError( string.Format("[REDIS] error connecting to {0}:{1}, {2}", host, port, ex.Message), ex); } throw; } } return _Connection; } }
private static RedisConnection SelectAndCreateConnection(string configuration, TextWriter log, out string selectedConfiguration, out string[] availableEndpoints, bool autoMaster, string newMaster = null, string tieBreakerKey = null) { if (tieBreakerKey == null) { tieBreakerKey = "__Booksleeve_TieBreak"; // default tie-breaker key } int syncTimeout; bool allowAdmin; string serviceName; string clientName; if (log == null) { log = new StringWriter(); } string[] arr = GetConfigurationOptions(configuration, out syncTimeout, out allowAdmin, out serviceName, out clientName); if (!string.IsNullOrWhiteSpace(newMaster)) { allowAdmin = true; // need this to diddle the slave/master config } log.WriteLine("{0} unique nodes specified", arr.Length); log.WriteLine("sync timeout: {0}ms, admin commands: {1}", syncTimeout, allowAdmin ? "enabled" : "disabled"); if (!string.IsNullOrEmpty(serviceName)) { log.WriteLine("service: {0}", serviceName); } if (!string.IsNullOrEmpty(clientName)) { log.WriteLine("client: {0}", clientName); } if (arr.Length == 0) { log.WriteLine("No nodes to consider"); selectedConfiguration = null; availableEndpoints = new string[0]; return(null); } var connections = new List <RedisConnection>(arr.Length); RedisConnection preferred = null; try { var infos = new List <Task <string> >(arr.Length); var tiebreakers = new List <Task <string> >(arr.Length); foreach (string option in arr) { if (string.IsNullOrWhiteSpace(option)) { continue; } RedisConnection conn = null; try { string[] parts = option.Split(':'); if (parts.Length == 0) { continue; } string host = parts[0].Trim(); int port = 6379, tmp; if (parts.Length > 1 && int.TryParse(parts[1].Trim(), out tmp)) { port = tmp; } conn = new RedisConnection(host, port, syncTimeout: syncTimeout, allowAdmin: allowAdmin); conn.Name = clientName; log.WriteLine("Opening connection to {0}:{1}...", host, port); conn.Open(); Task <string> info = conn.GetInfo(); Task <string> tiebreak = conn.Strings.GetString(0, tieBreakerKey); connections.Add(conn); infos.Add(info); tiebreakers.Add(tiebreak); } catch (Exception ex) { if (conn == null) { log.WriteLine("Error parsing option \"{0}\": {1}", option, ex.Message); } else { log.WriteLine("Error connecting: {0}", ex.Message); } } } List <RedisConnection> masters = new List <RedisConnection>(), slaves = new List <RedisConnection>(); var breakerScores = new Dictionary <string, int>(); foreach (var tiebreak in tiebreakers) { try { if (tiebreak.Wait(syncTimeout)) { string key = tiebreak.Result; if (string.IsNullOrWhiteSpace(key)) { continue; } int score; if (breakerScores.TryGetValue(key, out score)) { breakerScores[key] = score + 1; } else { breakerScores.Add(key, 1); } } } catch { /* if a node is down, that's fine too */ } } // see if any of our nodes are sentinels that know about the named service List <Tuple <RedisConnection, Task <Tuple <string, int> > > > sentinelNodes = null; foreach (RedisConnection conn in connections) { // the "wait" we did during tie-breaker detection means we should now know what each server is if (conn.ServerType == ServerType.Sentinel) { if (string.IsNullOrEmpty(serviceName)) { log.WriteLine("Sentinel discovered, but no serviceName was specified; ignoring {0}:{1}", conn.Host, conn.Port); } else { log.WriteLine("Querying sentinel {0}:{1} for {2}...", conn.Host, conn.Port, serviceName); if (sentinelNodes == null) { sentinelNodes = new List <Tuple <RedisConnection, Task <Tuple <string, int> > > >(); } sentinelNodes.Add(Tuple.Create(conn, conn.QuerySentinelMaster(serviceName))); } } } // wait for sentinel results, if any if (sentinelNodes != null) { var discoveredPairs = new Dictionary <Tuple <string, int>, int>(); foreach (var pair in sentinelNodes) { RedisConnection conn = pair.Item1; try { Tuple <string, int> master = conn.Wait(pair.Item2); if (master == null) { log.WriteLine("Sentinel {0}:{1} is not configured for {2}", conn.Host, conn.Port, serviceName); } else { log.WriteLine("Sentinel {0}:{1} nominates {2}:{3}", conn.Host, conn.Port, master.Item1, master.Item2); int count; if (discoveredPairs.TryGetValue(master, out count)) { count = 0; } discoveredPairs[master] = count + 1; } } catch (Exception ex) { log.WriteLine("Error from sentinel {0}:{1} - {2}", conn.Host, conn.Port, ex.Message); } } Tuple <string, int> finalChoice; switch (discoveredPairs.Count) { case 0: log.WriteLine("No sentinels nominated a master; unable to connect"); finalChoice = null; break; case 1: finalChoice = discoveredPairs.Single().Key; log.WriteLine("Sentinels nominated unanimous master: {0}:{1}", finalChoice.Item1, finalChoice.Item2); break; default: finalChoice = discoveredPairs.OrderByDescending(kvp => kvp.Value).First().Key; log.WriteLine("Sentinels nominated multiple masters; choosing arbitrarily: {0}:{1}", finalChoice.Item1, finalChoice.Item2); break; } if (finalChoice != null) { RedisConnection toBeDisposed = null; try { // good bet that in this scenario the input didn't specify any actual redis servers, so we'll assume open a new one log.WriteLine("Opening nominated master: {0}:{1}...", finalChoice.Item1, finalChoice.Item2); toBeDisposed = new RedisConnection(finalChoice.Item1, finalChoice.Item2, allowAdmin: allowAdmin, syncTimeout: syncTimeout); toBeDisposed.Wait(toBeDisposed.Open()); if (toBeDisposed.ServerType == ServerType.Master) { RedisConnection tmp = toBeDisposed; toBeDisposed = null; // so we don't dispose it selectedConfiguration = tmp.Host + ":" + tmp.Port; availableEndpoints = new[] { selectedConfiguration }; return(tmp); } else { log.WriteLine("Server is {0} instead of a master", toBeDisposed.ServerType); } } catch (Exception ex) { log.WriteLine("Error: {0}", ex.Message); } finally { // dispose if something went sour using (toBeDisposed) { } } } // something went south; BUT SENTINEL WINS TRUMPS; quit now selectedConfiguration = null; availableEndpoints = new string[0]; return(null); } // check for tie-breakers (i.e. when we store which is the master) switch (breakerScores.Count) { case 0: log.WriteLine("No tie-breakers found ({0})", tieBreakerKey); break; case 1: log.WriteLine("Tie-breaker ({0}) is unanimous: {1}", tieBreakerKey, breakerScores.Keys.Single()); break; default: log.WriteLine("Ambiguous tie-breakers ({0}):", tieBreakerKey); foreach (var kvp in breakerScores.OrderByDescending(x => x.Value)) { log.WriteLine("\t{0}: {1}", kvp.Key, kvp.Value); } break; } for (int i = 0; i < connections.Count; i++) { log.WriteLine("Reading configuration from {0}:{1}...", connections[i].Host, connections[i].Port); try { if (!infos[i].Wait(syncTimeout)) { log.WriteLine("\tTimeout fetching INFO"); continue; } var infoPairs = new StringDictionary(); using (var sr = new StringReader(infos[i].Result)) { string line; while ((line = sr.ReadLine()) != null) { int idx = line.IndexOf(':'); if (idx < 0) { continue; } string key = line.Substring(0, idx).Trim(), value = line.Substring(idx + 1, line.Length - (idx + 1)).Trim(); infoPairs[key] = value; } } string role = infoPairs["role"]; switch (role) { case "slave": log.WriteLine("\tServer is SLAVE of {0}:{1}", infoPairs["master_host"], infoPairs["master_port"]); log.Write("\tLink is {0}, seen {1} seconds ago", infoPairs["master_link_status"], infoPairs["master_last_io_seconds_ago"]); if (infoPairs["master_sync_in_progress"] == "1") { log.Write(" (sync is in progress)"); } log.WriteLine(); slaves.Add(connections[i]); break; case "master": log.WriteLine("\tServer is MASTER, with {0} slaves", infoPairs["connected_slaves"]); masters.Add(connections[i]); break; default: log.WriteLine("\tUnknown role: {0}", role); break; } string tmp = infoPairs["connected_clients"]; int clientCount, channelCount, patternCount; if (string.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out clientCount)) { clientCount = -1; } tmp = infoPairs["pubsub_channels"]; if (string.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out channelCount)) { channelCount = -1; } tmp = infoPairs["pubsub_patterns"]; if (string.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out patternCount)) { patternCount = -1; } log.WriteLine("\tClients: {0}; channels: {1}; patterns: {2}", clientCount, channelCount, patternCount); } catch (Exception ex) { log.WriteLine("\tError reading INFO results: {0}", ex.Message); } } if (newMaster == null) { switch (masters.Count) { case 0: switch (slaves.Count) { case 0: log.WriteLine("No masters or slaves found"); break; case 1: log.WriteLine("No masters found; selecting single slave"); preferred = slaves[0]; break; default: log.WriteLine("No masters found; considering {0} slaves...", slaves.Count); preferred = SelectWithTieBreak(log, slaves, breakerScores); break; } if (preferred != null) { if (autoMaster) { //LogException("Promoting redis SLAVE to MASTER"); log.WriteLine("Promoting slave to master..."); if (allowAdmin) { // can do on this connection preferred.Wait(preferred.Server.MakeMaster()); } else { // need an admin connection for this using ( var adminPreferred = new RedisConnection(preferred.Host, preferred.Port, allowAdmin: true, syncTimeout: syncTimeout)) { adminPreferred.Open(); adminPreferred.Wait(adminPreferred.Server.MakeMaster()); } } } else { log.WriteLine("Slave should be promoted to master (but not done yet)..."); } } break; case 1: log.WriteLine("One master found; selecting"); preferred = masters[0]; break; default: log.WriteLine("Considering {0} masters...", masters.Count); preferred = SelectWithTieBreak(log, masters, breakerScores); break; } } else { // we have been instructed to change master server preferred = masters.Concat(slaves) .FirstOrDefault(conn => (conn.Host + ":" + conn.Port) == newMaster); if (preferred == null) { log.WriteLine("Selected new master not available: {0}", newMaster); } else { int errorCount = 0; try { log.WriteLine("Promoting to master: {0}:{1}...", preferred.Host, preferred.Port); preferred.Wait(preferred.Server.MakeMaster()); // if this is a master, we expect set/publish to work, even on 2.6 preferred.Strings.Set(0, tieBreakerKey, newMaster); preferred.Wait(preferred.Publish(RedisMasterChangedChannel, newMaster)); } catch (Exception ex) { log.WriteLine("\t{0}", ex.Message); errorCount++; } if (errorCount == 0) // only make slaves if the master was happy { foreach (RedisConnection conn in masters.Concat(slaves)) { if (conn == preferred) { continue; // can't make self a slave! } try { log.WriteLine("Enslaving: {0}:{1}...", conn.Host, conn.Port); // try to set the tie-breaker **first** in case of problems Task didSet = conn.Strings.Set(0, tieBreakerKey, newMaster); // and broadcast to anyone who thinks this is the master Task <long> didPublish = conn.Publish(RedisMasterChangedChannel, newMaster); // now make it a slave Task didEnslave = conn.Server.MakeSlave(preferred.Host, preferred.Port); // these are best-effort only; from 2.6, readonly slave servers may reject these commands try { conn.Wait(didSet); } catch { } try { conn.Wait(didPublish); } catch { } // but this one we'll log etc conn.Wait(didEnslave); } catch (Exception ex) { log.WriteLine("\t{0}", ex.Message); errorCount++; } } } if (errorCount != 0) { log.WriteLine("Things didn't go smoothly; CHECK WHAT HAPPENED!"); } // want the connection disposed etc preferred = null; } } if (preferred == null) { selectedConfiguration = null; } else { selectedConfiguration = preferred.Host + ":" + preferred.Port; log.WriteLine("Selected server {0}", selectedConfiguration); } availableEndpoints = (from conn in masters.Concat(slaves) select conn.Host + ":" + conn.Port).ToArray(); return(preferred); } finally { foreach (RedisConnection conn in connections) { if (conn != null && conn != preferred) { try { conn.Dispose(); } catch { } } } } }
public void TestLockOpCountByVersion(RedisConnection conn, int expected, bool existFirst) { const int DB = 0, LockDuration = 30; const string Key = "TestOpCountByVersion"; conn.Wait(conn.Open()); conn.Keys.Remove(DB, Key); var newVal = "us:" + Config.CreateUniqueName(); string expectedVal = newVal; if (existFirst) { expectedVal = "other:" + Config.CreateUniqueName(); conn.Strings.Set(DB, Key, expectedVal, LockDuration); } int countBefore = conn.GetCounters().MessagesSent; var taken = conn.Wait(conn.Strings.TakeLock(DB, Key, newVal, LockDuration)); int countAfter = conn.GetCounters().MessagesSent; var valAfter = conn.Wait(conn.Strings.GetString(DB, Key)); Assert.AreEqual(!existFirst, taken, "lock taken"); Assert.AreEqual(expectedVal, valAfter, "taker"); Assert.AreEqual(expected, (countAfter - countBefore) - 1, "expected ops (including ping)"); // note we get a ping from GetCounters }
private static RedisConnection SelectAndCreateConnection(string configuration, TextWriter log, out string selectedConfiguration, out string[] availableEndpoints, bool autoMaster, string newMaster = null) { int syncTimeout; bool allowAdmin; if(log == null) log = new StringWriter(); var arr = GetConfigurationOptions(configuration, out syncTimeout, out allowAdmin); if (!string.IsNullOrWhiteSpace(newMaster)) allowAdmin = true; // need this to diddle the slave/master config log.WriteLine("{0} unique nodes specified", arr.Length); log.WriteLine("sync timeout: {0}ms, admin commands: {1}", syncTimeout, allowAdmin ? "enabled" : "disabled"); if (arr.Length == 0) { log.WriteLine("No nodes to consider"); selectedConfiguration = null; availableEndpoints = new string[0]; return null; } var connections = new List<RedisConnection>(arr.Length); RedisConnection preferred = null; try { var infos = new List<Task<string>>(arr.Length); var tiebreakers = new List<Task<string>>(arr.Length); foreach (var option in arr) { if (string.IsNullOrWhiteSpace(option)) continue; RedisConnection conn = null; try { var parts = option.Split(':'); if (parts.Length == 0) continue; string host = parts[0].Trim(); int port = 6379, tmp; if (parts.Length > 1 && int.TryParse(parts[1].Trim(), out tmp)) port = tmp; conn = new RedisConnection(host, port, syncTimeout: syncTimeout, allowAdmin: allowAdmin); log.WriteLine("Opening connection to {0}:{1}...", host, port); conn.Open(); var info = conn.GetInfo(); var tiebreak = conn.Strings.GetString(0, TieBreakerKey); connections.Add(conn); infos.Add(info); tiebreakers.Add(tiebreak); } catch (Exception ex) { if (conn == null) { log.WriteLine("Error parsing option \"{0}\": {1}", option, ex.Message); } else { log.WriteLine("Error connecting: {0}", ex.Message); } } } List<RedisConnection> masters = new List<RedisConnection>(), slaves = new List<RedisConnection>(); var breakerScores = new Dictionary<string, int>(); foreach (var tiebreak in tiebreakers) { try { if (tiebreak.Wait(syncTimeout)) { string key = tiebreak.Result; if (string.IsNullOrWhiteSpace(key)) continue; int score; if (breakerScores.TryGetValue(key, out score)) breakerScores[key] = score + 1; else breakerScores.Add(key, 1); } } catch { /* if a node is down, that's fine too */ } } // check for tie-breakers (i.e. when we store which is the master) switch (breakerScores.Count) { case 0: log.WriteLine("No tie-breakers found"); break; case 1: log.WriteLine("Tie-breaker is unanimous: {0}", breakerScores.Keys.Single()); break; default: log.WriteLine("Ambiguous tie-breakers:"); foreach (var kvp in breakerScores.OrderByDescending(x => x.Value)) { log.WriteLine("\t{0}: {1}", kvp.Key, kvp.Value); } break; } for (int i = 0; i < connections.Count; i++) { log.WriteLine("Reading configuration from {0}:{1}...", connections[i].Host, connections[i].Port); try { if (!infos[i].Wait(syncTimeout)) { log.WriteLine("\tTimeout fetching INFO"); continue; } var infoPairs = new StringDictionary(); using (var sr = new StringReader(infos[i].Result)) { string line; while ((line = sr.ReadLine()) != null) { int idx = line.IndexOf(':'); if (idx < 0) continue; string key = line.Substring(0, idx).Trim(), value = line.Substring(idx + 1, line.Length - (idx + 1)).Trim(); infoPairs[key] = value; } } string role = infoPairs["role"]; switch (role) { case "slave": log.WriteLine("\tServer is SLAVE of {0}:{1}", infoPairs["master_host"], infoPairs["master_port"]); log.Write("\tLink is {0}, seen {1} seconds ago", infoPairs["master_link_status"], infoPairs["master_last_io_seconds_ago"]); if (infoPairs["master_sync_in_progress"] == "1") log.Write(" (sync is in progress)"); log.WriteLine(); slaves.Add(connections[i]); break; case "master": log.WriteLine("\tServer is MASTER, with {0} slaves", infoPairs["connected_slaves"]); masters.Add(connections[i]); break; default: log.WriteLine("\tUnknown role: {0}", role); break; } string tmp = infoPairs["connected_clients"]; int clientCount, channelCount, patternCount; if (string.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out clientCount)) clientCount = -1; tmp = infoPairs["pubsub_channels"]; if (string.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out channelCount)) channelCount = -1; tmp = infoPairs["pubsub_patterns"]; if (string.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out patternCount)) patternCount = -1; log.WriteLine("\tClients: {0}; channels: {1}; patterns: {2}", clientCount, channelCount, patternCount); } catch (Exception ex) { log.WriteLine("\tError reading INFO results: {0}", ex.Message); } } if (newMaster == null) { switch (masters.Count) { case 0: switch (slaves.Count) { case 0: log.WriteLine("No masters or slaves found"); break; case 1: log.WriteLine("No masters found; selecting single slave"); preferred = slaves[0]; break; default: log.WriteLine("No masters found; considering {0} slaves...", slaves.Count); preferred = SelectWithTieBreak(log, slaves, breakerScores); break; } if (preferred != null) { if (autoMaster) { //LogException("Promoting redis SLAVE to MASTER"); log.WriteLine("Promoting slave to master..."); if (allowAdmin) { // can do on this connection preferred.Wait(preferred.Server.MakeMaster()); } else { // need an admin connection for this using (var adminPreferred = new RedisConnection(preferred.Host, preferred.Port, allowAdmin: true, syncTimeout: syncTimeout)) { adminPreferred.Open(); adminPreferred.Wait(adminPreferred.Server.MakeMaster()); } } } else { log.WriteLine("Slave should be promoted to master (but not done yet)..."); } } break; case 1: log.WriteLine("One master found; selecting"); preferred = masters[0]; break; default: log.WriteLine("Considering {0} masters...", masters.Count); preferred = SelectWithTieBreak(log, masters, breakerScores); break; } } else { // we have been instructed to change master server preferred = masters.Concat(slaves).FirstOrDefault(conn => (conn.Host + ":" + conn.Port) == newMaster); if (preferred == null) { log.WriteLine("Selected new master not available: {0}", newMaster); } else { int errorCount = 0; try { log.WriteLine("Promoting to master: {0}:{1}...", preferred.Host, preferred.Port); preferred.Wait(preferred.Server.MakeMaster()); preferred.Strings.Set(0, TieBreakerKey, newMaster); preferred.Wait(preferred.Publish(RedisMasterChangedChannel, newMaster)); } catch (Exception ex) { log.WriteLine("\t{0}", ex.Message); errorCount++; } if (errorCount == 0) // only make slaves if the master was happy { foreach (var conn in masters.Concat(slaves)) { if (conn == preferred) continue; // can't make self a slave! try { log.WriteLine("Enslaving: {0}:{1}...", conn.Host, conn.Port); // set the tie-breaker **first** in case of problems conn.Strings.Set(0, TieBreakerKey, newMaster); // and broadcast to anyone who thinks this is the master conn.Publish(RedisMasterChangedChannel, newMaster); // now make it a slave conn.Wait(conn.Server.MakeSlave(preferred.Host, preferred.Port)); } catch (Exception ex) { log.WriteLine("\t{0}",ex.Message); errorCount++; } } } if (errorCount != 0) { log.WriteLine("Things didn't go smoothly; CHECK WHAT HAPPENED!"); } // want the connection disposed etc preferred = null; } } if (preferred == null) { selectedConfiguration = null; } else { selectedConfiguration = preferred.Host + ":" + preferred.Port; log.WriteLine("Selected server {0}", selectedConfiguration); } availableEndpoints = (from conn in masters.Concat(slaves) select conn.Host + ":" + conn.Port).ToArray(); return preferred; } finally { foreach (var conn in connections) { if (conn != null && conn != preferred) try { conn.Dispose(); } catch { } } } }
internal static RedisConnection SelectAndCreateConnection(string configuration, TextWriter log, out string selectedConfiguration, out string[] availableEndpoints, bool autoMaster, string newMaster = null, string tieBreakerKey = null) { TraceWriteTime("Start: " + configuration); if (tieBreakerKey == null) tieBreakerKey = "__Booksleeve_TieBreak"; // default tie-breaker key int syncTimeout; int keepAlive; bool allowAdmin; string serviceName; string clientName; if(log == null) log = new StringWriter(); var arr = GetConfigurationOptions(configuration, out syncTimeout, out allowAdmin, out serviceName, out clientName, out keepAlive); if (!string.IsNullOrWhiteSpace(newMaster)) allowAdmin = true; // need this to diddle the slave/master config log.WriteLine("{0} unique nodes specified", arr.Length); log.WriteLine("sync timeout: {0}ms, admin commands: {1}", syncTimeout, allowAdmin ? "enabled" : "disabled"); if (!string.IsNullOrEmpty(serviceName)) log.WriteLine("service: {0}", serviceName); if (!string.IsNullOrEmpty(clientName)) log.WriteLine("client: {0}", clientName); if (arr.Length == 0) { log.WriteLine("No nodes to consider"); selectedConfiguration = null; availableEndpoints = new string[0]; return null; } var connections = new List<RedisConnection>(arr.Length); RedisConnection preferred = null; try { Task<string>[] infos, tiebreakers; List<RedisConnection> masters = new List<RedisConnection>(arr.Length); List<RedisConnection> slaves = new List<RedisConnection>(arr.Length); Dictionary<string, int> breakerScores = new Dictionary<string, int>(arr.Length); ConnectToNodes(log, tieBreakerKey, syncTimeout, keepAlive, allowAdmin, clientName, arr, connections, out infos, out tiebreakers, AuxMode.TieBreakers); for (int i = 0; i < tiebreakers.Length; i++ ) { var tiebreak = tiebreakers[i]; try { if (tiebreak.IsCompleted) { string key = tiebreak.Result; if (string.IsNullOrWhiteSpace(key)) continue; int score; if (breakerScores.TryGetValue(key, out score)) breakerScores[key] = score + 1; else breakerScores.Add(key, 1); } else { infos[i] = null; // forget it; took too long if (tiebreak.IsFaulted) { GC.KeepAlive(tiebreak.Exception); // just an opaque method to show we've looked } } } catch { /* if a node is down, that's fine too */ } } TraceWriteTime("Check for sentinels"); // see if any of our nodes are sentinels that know about the named service List<Tuple<RedisConnection, Task<Tuple<string, int>>>> sentinelNodes = null; foreach (var conn in connections) { // the "wait" we did during tie-breaker detection means we should now know what each server is if (conn.ServerType == ServerType.Sentinel) { if (string.IsNullOrEmpty(serviceName)) { log.WriteLine("Sentinel discovered, but no serviceName was specified; ignoring {0}:{1}", conn.Host, conn.Port); } else { log.WriteLine("Querying sentinel {0}:{1} for {2}...", conn.Host, conn.Port, serviceName); if (sentinelNodes == null) sentinelNodes = new List<Tuple<RedisConnection, Task<Tuple<string, int>>>>(); sentinelNodes.Add(Tuple.Create(conn, conn.QuerySentinelMaster(serviceName))); } } } // wait for sentinel results, if any if(sentinelNodes != null) { var discoveredPairs = new Dictionary<Tuple<string, int>, int>(); foreach(var pair in sentinelNodes) { var conn = pair.Item1; try { var master = conn.Wait(pair.Item2); if(master == null) { log.WriteLine("Sentinel {0}:{1} is not configured for {2}", conn.Host, conn.Port, serviceName); } else { log.WriteLine("Sentinel {0}:{1} nominates {2}:{3}", conn.Host, conn.Port, master.Item1, master.Item2); int count; if (discoveredPairs.TryGetValue(master, out count)) count = 0; discoveredPairs[master] = count + 1; } } catch (Exception ex) { log.WriteLine("Error from sentinel {0}:{1} - {2}", conn.Host, conn.Port, ex.Message); } } Tuple<string, int> finalChoice; switch (discoveredPairs.Count) { case 0: log.WriteLine("No sentinels nominated a master; unable to connect"); finalChoice = null; break; case 1: finalChoice = discoveredPairs.Single().Key; log.WriteLine("Sentinels nominated unanimous master: {0}:{1}", finalChoice.Item1, finalChoice.Item2); break; default: finalChoice = discoveredPairs.OrderByDescending(kvp => kvp.Value).First().Key; log.WriteLine("Sentinels nominated multiple masters; choosing arbitrarily: {0}:{1}", finalChoice.Item1, finalChoice.Item2); break; } if (finalChoice != null) { RedisConnection toBeDisposed = null; try { // good bet that in this scenario the input didn't specify any actual redis servers, so we'll assume open a new one log.WriteLine("Opening nominated master: {0}:{1}...", finalChoice.Item1, finalChoice.Item2); toBeDisposed = new RedisConnection(finalChoice.Item1, finalChoice.Item2, allowAdmin: allowAdmin, syncTimeout: syncTimeout); if (keepAlive >= 0) toBeDisposed.SetKeepAlive(keepAlive); toBeDisposed.Wait(toBeDisposed.Open()); if (toBeDisposed.ServerType == ServerType.Master) { var tmp = toBeDisposed; toBeDisposed = null; // so we don't dispose it selectedConfiguration = tmp.Host + ":" + tmp.Port; availableEndpoints = new string[] { selectedConfiguration }; return tmp; } else { log.WriteLine("Server is {0} instead of a master", toBeDisposed.ServerType); } } catch (Exception ex) { log.WriteLine("Error: {0}", ex.Message); } finally { // dispose if something went sour using (toBeDisposed) { } } } // something went south; BUT SENTINEL WINS TRUMPS; quit now selectedConfiguration = null; availableEndpoints = new string[0]; return null; } TraceWriteTime("Check tie-breakers"); // check for tie-breakers (i.e. when we store which is the master) switch (breakerScores.Count) { case 0: log.WriteLine("No tie-breakers found ({0})", tieBreakerKey); break; case 1: log.WriteLine("Tie-breaker ({0}) is unanimous: {1}", tieBreakerKey, breakerScores.Keys.Single()); break; default: log.WriteLine("Ambiguous tie-breakers ({0}):", tieBreakerKey); foreach (var kvp in breakerScores.OrderByDescending(x => x.Value)) { log.WriteLine("\t{0}: {1}", kvp.Key, kvp.Value); } break; } TraceWriteTime("Check connections"); for (int i = 0; i < connections.Count; i++) { if (infos[i] == null) { log.WriteLine("Server did not respond - {0}:{1}...", connections[i].Host, connections[i].Port); connections[i].Close(abort:true); continue; } log.WriteLine("Reading configuration from {0}:{1}...", connections[i].Host, connections[i].Port); try { if (!infos[i].Wait(syncTimeout)) { log.WriteLine("\tTimeout fetching INFO"); continue; } var infoPairs = new StringDictionary(); using (var sr = new StringReader(infos[i].Result)) { string line; while ((line = sr.ReadLine()) != null) { int idx = line.IndexOf(':'); if (idx < 0) continue; string key = line.Substring(0, idx).Trim(), value = line.Substring(idx + 1, line.Length - (idx + 1)).Trim(); infoPairs[key] = value; } } string role = infoPairs["role"]; switch (role) { case "slave": log.WriteLine("\tServer is SLAVE of {0}:{1}", infoPairs["master_host"], infoPairs["master_port"]); log.Write("\tLink is {0}, seen {1} seconds ago", infoPairs["master_link_status"], infoPairs["master_last_io_seconds_ago"]); if (infoPairs["master_sync_in_progress"] == "1") log.Write(" (sync is in progress)"); log.WriteLine(); slaves.Add(connections[i]); break; case "master": log.WriteLine("\tServer is MASTER, with {0} slaves", infoPairs["connected_slaves"]); masters.Add(connections[i]); break; default: if (!string.IsNullOrWhiteSpace(role)) { log.WriteLine("\tUnknown role: {0}", role); } break; } string tmp = infoPairs["connected_clients"]; int clientCount, channelCount, patternCount; if (string.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out clientCount)) clientCount = -1; tmp = infoPairs["pubsub_channels"]; if (string.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out channelCount)) channelCount = -1; tmp = infoPairs["pubsub_patterns"]; if (string.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out patternCount)) patternCount = -1; log.WriteLine("\tClients: {0}; channels: {1}; patterns: {2}", clientCount, channelCount, patternCount); } catch (Exception ex) { log.WriteLine("\tError reading INFO results: {0}", ex.Message); } } TraceWriteTime("Check masters"); if (newMaster == null) { switch (masters.Count) { case 0: switch (slaves.Count) { case 0: log.WriteLine("No masters or slaves found"); break; case 1: log.WriteLine("No masters found; selecting single slave"); preferred = slaves[0]; break; default: log.WriteLine("No masters found; considering {0} slaves...", slaves.Count); preferred = SelectWithTieBreak(log, slaves, breakerScores); break; } if (preferred != null) { if (autoMaster) { //LogException("Promoting redis SLAVE to MASTER"); log.WriteLine("Promoting slave to master..."); if (allowAdmin) { // can do on this connection preferred.Wait(preferred.Server.MakeMaster()); } else { // need an admin connection for this using (var adminPreferred = new RedisConnection(preferred.Host, preferred.Port, allowAdmin: true, syncTimeout: syncTimeout)) { adminPreferred.Open(); adminPreferred.Wait(adminPreferred.Server.MakeMaster()); } } } else { log.WriteLine("Slave should be promoted to master (but not done yet)..."); } } break; case 1: log.WriteLine("One master found; selecting"); preferred = masters[0]; break; default: log.WriteLine("Considering {0} masters...", masters.Count); preferred = SelectWithTieBreak(log, masters, breakerScores); break; } } else { // we have been instructed to change master server preferred = masters.Concat(slaves).FirstOrDefault(conn => (conn.Host + ":" + conn.Port) == newMaster); if (preferred == null) { log.WriteLine("Selected new master not available: {0}", newMaster); } else { int errorCount = 0; try { log.WriteLine("Promoting to master: {0}:{1}...", preferred.Host, preferred.Port); preferred.Wait(preferred.Server.MakeMaster()); // if this is a master, we expect set/publish to work, even on 2.6 preferred.Strings.Set(0, tieBreakerKey, newMaster); preferred.Wait(preferred.Publish(RedisMasterChangedChannel, newMaster)); } catch (Exception ex) { log.WriteLine("\t{0}", ex.Message); errorCount++; } if (errorCount == 0) // only make slaves if the master was happy { foreach (var conn in masters.Concat(slaves)) { if (conn == preferred) continue; // can't make self a slave! try { log.WriteLine("Enslaving: {0}:{1}...", conn.Host, conn.Port); // try to set the tie-breaker **first** in case of problems var didSet = conn.Strings.Set(0, tieBreakerKey, newMaster); // and broadcast to anyone who thinks this is the master var didPublish = conn.Publish(RedisMasterChangedChannel, newMaster); // now make it a slave var didEnslave = conn.Server.MakeSlave(preferred.Host, preferred.Port); // these are best-effort only; from 2.6, readonly slave servers may reject these commands try { conn.Wait(didSet); } catch {} try { conn.Wait(didPublish); } catch {} // but this one we'll log etc conn.Wait(didEnslave); } catch (Exception ex) { log.WriteLine("\t{0}",ex.Message); errorCount++; } } } if (errorCount != 0) { log.WriteLine("Things didn't go smoothly; CHECK WHAT HAPPENED!"); } // want the connection disposed etc preferred = null; } } TraceWriteTime("Outro"); if (preferred == null) { selectedConfiguration = null; } else { selectedConfiguration = preferred.Host + ":" + preferred.Port; log.WriteLine("Selected server {0}", selectedConfiguration); } availableEndpoints = (from conn in masters.Concat(slaves) select conn.Host + ":" + conn.Port).ToArray(); TraceWriteTime("Return"); return preferred; } finally { TraceWriteTime("Start cleanup"); foreach (var conn in connections) { if (conn != null && conn != preferred) try { conn.Dispose(); } catch { } } TraceWriteTime("End cleanup"); } }
/// <summary> /// Prompt all clients to reconnect. /// </summary> public static void BroadcastReconnectMessage(RedisConnection connection) { if(connection == null) throw new ArgumentNullException("connection"); connection.Wait(connection.Publish(RedisMasterChangedChannel, "*")); }