public IEnumerable <Message> GetMessages(PhysicalConnection connection) { ResultBox lastBox = null; try { // Important: if the server supports EXECABORT, then we can check the pre-conditions (pause there), // which will usually be pretty small and cheap to do - if that passes, we can just isue all the commands // and rely on EXECABORT to kick us if we are being idiotic inside the MULTI. However, if the server does // *not* support EXECABORT, then we need to explicitly check for QUEUED anyway; we might as well defer // checking the preconditions to the same time to avoid having to pause twice. This will mean that on // up-version servers, pre-condition failures exit with UNWATCH; and on down-version servers pre-condition // failures exit with DISCARD - but that's ok : both work fine bool explicitCheckForQueued = !connection.Bridge.ServerEndPoint.GetFeatures().ExecAbort; var multiplexer = connection.Multiplexer; // PART 1: issue the pre-conditions if (!IsAborted && conditions.Length != 0) { for (int i = 0; i < conditions.Length; i++) { // need to have locked them before sending them // to guarantee that we see the pulse ResultBox latestBox = conditions[i].GetBox(); Monitor.Enter(latestBox); if (lastBox != null) { Monitor.Exit(lastBox); } lastBox = latestBox; foreach (var msg in conditions[i].CreateMessages(Db)) { msg.SetNoRedirect(); // need to keep them in the current context only yield return(msg); } } if (!explicitCheckForQueued && lastBox != null) { // need to get those sent ASAP; if they are stuck in the buffers, we die multiplexer.Trace("Flushing and waiting for precondition responses"); connection.Flush(); if (Monitor.Wait(lastBox, multiplexer.TimeoutMilliseconds)) { if (!AreAllConditionsSatisfied(multiplexer)) { command = RedisCommand.UNWATCH; // somebody isn't happy } } else { // timeout running pre-conditions multiplexer.Trace("Timeout checking preconditions"); command = RedisCommand.UNWATCH; } Monitor.Exit(lastBox); lastBox = null; } } // PART 2: begin the transaction if (!IsAborted) { multiplexer.Trace("Begining transaction"); yield return(Message.Create(-1, CommandFlags.None, RedisCommand.MULTI)); } // PART 3: issue the commands if (!IsAborted && operations.Length != 0) { multiplexer.Trace("Issuing transaction operations"); foreach (var op in operations) { if (explicitCheckForQueued) { // need to have locked them before sending them // to guarantee that we see the pulse ResultBox thisBox = op.ResultBox; if (thisBox != null) { Monitor.Enter(thisBox); if (lastBox != null) { Monitor.Exit(lastBox); } lastBox = thisBox; } } yield return(op); } if (explicitCheckForQueued && lastBox != null) { multiplexer.Trace("Flushing and waiting for precondition+queued responses"); connection.Flush(); // make sure they get sent, so we can check for QUEUED (and the pre-conditions if necessary) if (Monitor.Wait(lastBox, multiplexer.TimeoutMilliseconds)) { if (!AreAllConditionsSatisfied(multiplexer)) { command = RedisCommand.DISCARD; } else { foreach (var op in operations) { if (!op.WasQueued) { multiplexer.Trace("Aborting: operation was not queued: " + op.Command); command = RedisCommand.DISCARD; break; } } } multiplexer.Trace("Confirmed: QUEUED x " + operations.Length); } else { multiplexer.Trace("Aborting: timeout checking queued messages"); command = RedisCommand.DISCARD; } Monitor.Exit(lastBox); lastBox = null; } } } finally { if (lastBox != null) { Monitor.Exit(lastBox); } } if (IsAborted) { connection.Multiplexer.Trace("Aborting: canceling wrapped messages"); var bridge = connection.Bridge; foreach (var op in operations) { op.Wrapped.Cancel(); bridge.CompleteSyncOrAsync(op.Wrapped); } } connection.Multiplexer.Trace("End ot transaction: " + Command); yield return(this); // acts as either an EXEC or an UNWATCH, depending on "aborted" }
void Handshake(PhysicalConnection connection, TextWriter log) { multiplexer.LogLocked(log, "Server handshake"); if (connection == null) { multiplexer.Trace("No connection!?"); return; } Message msg; string password = multiplexer.RawConfig.Password; if (!string.IsNullOrWhiteSpace(password)) { multiplexer.LogLocked(log, "Authenticating (password)"); msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.AUTH, (RedisValue)password); msg.SetInternalCall(); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.DemandOK); } if (multiplexer.CommandMap.IsAvailable(RedisCommand.CLIENT)) { string name = multiplexer.ClientName; if (!string.IsNullOrWhiteSpace(name)) { name = nameSanitizer.Replace(name, ""); if (!string.IsNullOrWhiteSpace(name)) { multiplexer.LogLocked(log, "Setting client name: {0}", name); msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.CLIENT, RedisLiterals.SETNAME, (RedisValue)name); msg.SetInternalCall(); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.DemandOK); } } } var connType = connection.Bridge.ConnectionType; if (connType == ConnectionType.Interactive) { multiplexer.LogLocked(log, "Auto-configure..."); AutoConfigure(connection); } multiplexer.LogLocked(log, "Sending critical tracer: {0}", connection.Bridge); var tracer = GetTracerMessage(true); tracer = LoggingMessage.Create(log, tracer); WriteDirectOrQueueFireAndForget(connection, tracer, ResultProcessor.EstablishConnection); // note: this **must** be the last thing on the subscription handshake, because after this // we will be in subscriber mode: regular commands cannot be sent if (connType == ConnectionType.Subscription) { var configChannel = multiplexer.ConfigurationChangedChannel; if (configChannel != null) { msg = Message.Create(-1, CommandFlags.FireAndForget, RedisCommand.SUBSCRIBE, (RedisChannel)configChannel); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.TrackSubscriptions); } } multiplexer.LogLocked(log, "Flushing outbound buffer"); connection.Flush(); }
internal WriteResult WriteQueue(int maxWork) { bool weAreWriter = false; PhysicalConnection conn = null; try { Trace("Writing queue from bridge"); weAreWriter = Interlocked.CompareExchange(ref activeWriters, 1, 0) == 0; if (!weAreWriter) { Trace("(aborting: existing writer)"); return(WriteResult.CompetingWriter); } conn = GetConnection(null); if (conn == null) { AbortUnsent(); Trace("Connection not available; exiting"); return(WriteResult.NoConnection); } Message last; int count = 0; while (true) { var next = queue.Dequeue(); if (next == null) { Trace("Nothing to write; exiting"); if (count == 0) { conn.Flush(); // only flush on an empty run return(WriteResult.NothingToDo); } return(WriteResult.QueueEmptyAfterWrite); } last = next; Trace("Now pending: " + GetPendingCount()); if (!WriteMessageDirect(conn, next)) { AbortUnsent(); Trace("write failed; connection is toast; exiting"); return(WriteResult.NoConnection); } count++; if (maxWork > 0 && count >= maxWork) { Trace("Work limit; exiting"); Trace(last != null, "Flushed up to: " + last); conn.Flush(); break; } } } catch (IOException ex) { if (conn != null) { conn.RecordConnectionFailed(ConnectionFailureType.SocketFailure, ex); conn = null; } AbortUnsent(); } catch (Exception ex) { AbortUnsent(); OnInternalError(ex); } finally { if (weAreWriter) { Interlocked.Exchange(ref activeWriters, 0); Trace("Exiting writer"); } } return(queue.Any() ? WriteResult.MoreWork : WriteResult.QueueEmptyAfterWrite); }