Ejemplo n.º 1
0
        private static void ConnectToNodes(TextWriter log, string tieBreakerKey, int syncTimeout, int keepAlive, bool allowAdmin, string clientName, string[] arr, List<RedisConnection> connections, out Task<string>[] infos, out Task<string>[] aux, AuxMode mode)
        {
            TraceWriteTime("Infos");
            infos = new Task<string>[arr.Length];
            aux = new Task<string>[arr.Length];
            var opens = new Task[arr.Length];
            for (int i = 0; i < arr.Length; i++)
            {
                var option = arr[i];
                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);
                    conn.Name = clientName;
                    log.WriteLine("Opening connection to {0}:{1}...", host, port);
                    if (keepAlive >= 0) conn.SetKeepAlive(keepAlive);
                    opens[i] = conn.Open();
                    var info = conn.GetInfoImpl(null, false, false);
                    connections.Add(conn);
                    infos[i] = info;
                    switch (mode)
                    {
                        case AuxMode.TieBreakers:
                            if (tieBreakerKey != null)
                            {
                                aux[i] = conn.Strings.GetString(0, tieBreakerKey);
                            }
                            break;
                        case AuxMode.ClusterNodes:
                            aux[i] = conn.Cluster.GetNodes();
                            break;
                    }

                }
                catch (Exception ex)
                {
                    if (conn == null)
                    {
                        log.WriteLine("Error parsing option \"{0}\": {1}", option, ex.Message);
                    }
                    else
                    {
                        log.WriteLine("Error connecting: {0}", ex.Message);
                    }
                }
            }

            TraceWriteTime("Wait for infos");
            RedisConnectionBase.Trace("select-create", "wait...");
            var watch = new Stopwatch();
            foreach (Task task in infos.Concat(aux).Concat(opens))
            {
                if (task != null)
                {
                    try
                    {
                        int remaining = unchecked((int)(syncTimeout - watch.ElapsedMilliseconds));
                        if (remaining > 0) task.Wait(remaining);
                    }
                    catch { }
                }
            }
            watch.Stop();
            RedisConnectionBase.Trace("select-create", "complete");
        }
Ejemplo n.º 2
0
        private static void ConnectToNodes(TextWriter log, string tieBreakerKey, int syncTimeout, int keepAlive, bool allowAdmin, string clientName, string[] arr, List <RedisConnection> connections, out Task <string>[] infos, out Task <string>[] aux, AuxMode mode)
        {
            TraceWriteTime("Infos");
            infos = new Task <string> [arr.Length];
            aux   = new Task <string> [arr.Length];
            var opens = new Task[arr.Length];

            for (int i = 0; i < arr.Length; i++)
            {
                var option = arr[i];
                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);
                    conn.Name = clientName;
                    log.WriteLine("Opening connection to {0}:{1}...", host, port);
                    if (keepAlive >= 0)
                    {
                        conn.SetKeepAlive(keepAlive);
                    }
                    opens[i] = conn.Open();
                    var info = conn.GetInfoImpl(null, false, false);
                    connections.Add(conn);
                    infos[i] = info;
                    switch (mode)
                    {
                    case AuxMode.TieBreakers:
                        if (tieBreakerKey != null)
                        {
                            aux[i] = conn.Strings.GetString(0, tieBreakerKey);
                        }
                        break;

                    case AuxMode.ClusterNodes:
                        aux[i] = conn.Cluster.GetNodes();
                        break;
                    }
                }
                catch (Exception ex)
                {
                    if (conn == null)
                    {
                        log.WriteLine("Error parsing option \"{0}\": {1}", option, ex.Message);
                    }
                    else
                    {
                        log.WriteLine("Error connecting: {0}", ex.Message);
                    }
                }
            }

            TraceWriteTime("Wait for infos");
            RedisConnectionBase.Trace("select-create", "wait...");
            var watch = new Stopwatch();

            foreach (Task task in infos.Concat(aux).Concat(opens))
            {
                if (task != null)
                {
                    try
                    {
                        int remaining = unchecked ((int)(syncTimeout - watch.ElapsedMilliseconds));
                        if (remaining > 0)
                        {
                            task.Wait(remaining);
                        }
                    }
                    catch { }
                }
            }
            watch.Stop();
            RedisConnectionBase.Trace("select-create", "complete");
        }
Ejemplo n.º 3
0
        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");
            }
        }
Ejemplo n.º 4
0
        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");
            }
        }