private void SelectDatabase(PhysicalConnection connection, Message message) { int db = message.Db; if (db >= 0) { var sel = connection.GetSelectDatabaseCommand(db, message); if (sel != null) { connection.Enqueue(sel); sel.WriteImpl(connection); sel.SetRequestSent(); IncrementOpCount(); } } }
internal void WriteTo(PhysicalConnection physical) { try { WriteImpl(physical); } catch (RedisCommandException) { // these have specific meaning; don't wrap throw; } catch (Exception ex) { physical?.OnInternalError(ex); Fail(ConnectionFailureType.InternalFailure, ex); } }
internal void WriteDirectOrQueueFireAndForget <T>(PhysicalConnection connection, Message message, ResultProcessor <T> processor) { if (message != null) { message.SetSource(processor, null); if (connection == null) { multiplexer.Trace("Enqueue: " + message); GetBridge(message.Command).TryEnqueue(message, isSlave); } else { multiplexer.Trace("Writing direct: " + message); connection.Bridge.WriteMessageDirect(connection, message); } } }
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) { switch (result.Type) { case ResultType.MultiBulk: var arr = result.GetItems(); long i64; if (arr.Length == 2 && arr[1].Type == ResultType.MultiBulk && arr[0].TryGetInt64(out i64)) { var keysResult = new ScanResult(i64, arr[1].GetItemsAsKeys()); SetResult(message, keysResult); return(true); } break; } return(false); }
internal void OnConnected(PhysicalConnection connection, TextWriter log) { Trace("OnConnected"); if (physical == connection && !isDisposed && ChangeState(State.Connecting, State.ConnectedEstablishing)) { ServerEndPoint.OnEstablishing(connection, log); } else { try { connection.Dispose(); } catch { } } }
private static Action <Task <int> > EndReadFactory(PhysicalConnection physical) { return(result => { // can't capture AsyncState on SocketRead, so we'll do it once per physical instead try { physical.Multiplexer.Trace("Completed asynchronously: processing in callback", physical.physicalName); if (physical.EndReading(result)) { physical.BeginReading(); } } catch (Exception ex) { physical.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); } }); }
public override bool SetResult(PhysicalConnection connection, Message message, RawResult result) { if (result.IsError) { var tran = message as TransactionMessage; if (tran != null) { string error = result.GetString(); var bridge = connection.Bridge; foreach (var op in tran.InnerOperations) { ServerFail(op.Wrapped, error); bridge.CompleteSyncOrAsync(op.Wrapped); } } } return(base.SetResult(connection, message, result)); }
internal void OnFullyEstablished(PhysicalConnection connection) { try { if (connection == null) { return; } var bridge = connection.Bridge; if (bridge == subscription) { multiplexer.ResendSubscriptions(this); } multiplexer.OnConnectionRestored(endpoint, bridge.ConnectionType); } catch (Exception ex) { connection.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); } }
internal void OnFullyEstablished(PhysicalConnection connection) { Trace("OnFullyEstablished"); if (physical == connection && !isDisposed && ChangeState(State.ConnectedEstablishing, State.ConnectedEstablished)) { reportNextFailure = reconfigureNextFailure = true; LastException = null; Interlocked.Exchange(ref failConnectCount, 0); ServerEndPoint.OnFullyEstablished(connection); Multiplexer.RequestWrite(this, true); if (ConnectionType == ConnectionType.Interactive) { ServerEndPoint.CheckInfoReplication(); } } else { try { connection.Dispose(); } catch { } } }
// internally, this is very similar to RawResult, except it is designed to be usable // outside of the IO-processing pipeline: the buffers are standalone, etc internal static RedisResult TryCreate(PhysicalConnection connection, RawResult result) { try { switch (result.Type) { case ResultType.Integer: case ResultType.SimpleString: case ResultType.BulkString: return(new SingleRedisResult(result.AsRedisValue())); case ResultType.MultiBulk: var items = result.GetItems(); var arr = new RedisResult[items.Length]; for (int i = 0; i < arr.Length; i++) { var next = TryCreate(connection, items[i]); if (next == null) { return(null); // means we didn't understand } arr[i] = next; } return(new ArrayRedisResult(arr)); case ResultType.Error: return(new ErrorRedisResult(result.GetString())); default: return(null); } } catch (Exception ex) { connection?.OnInternalError(ex); return(null); // will be logged as a protocol fail by the processor } }
internal override void WriteImpl(PhysicalConnection physical) { physical.WriteHeader(Command, 2); physical.Write(Channel); physical.Write(value); }
internal void RemovePhysical(PhysicalConnection connection) { #pragma warning disable 0420 Interlocked.CompareExchange(ref physical, null, connection); #pragma warning restore 0420 }
internal abstract void WriteImpl(PhysicalConnection physical);
internal void Fail(ConnectionFailureType failure, Exception innerException) { PhysicalConnection.IdentifyFailureType(innerException, ref failure); resultProcessor?.ConnectionFail(this, failure, innerException); }
// true if ready to be completed (i.e. false if re-issued to another server) internal bool ComputeResult(PhysicalConnection connection, RawResult result) { return(resultProcessor == null || resultProcessor.SetResult(connection, this, result)); }
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 override void WriteImpl(PhysicalConnection physical) { wrapped.WriteImpl(physical); wrapped.SetRequestSent(); }
internal void AutoConfigure(PhysicalConnection connection) { if (serverType == ServerType.Twemproxy) { // don't try to detect configuration; all the config commands are disabled, and // the fallback master/slave detection won't help return; } var commandMap = multiplexer.CommandMap; const CommandFlags flags = CommandFlags.FireAndForget | CommandFlags.HighPriority | CommandFlags.NoRedirect; var features = GetFeatures(); Message msg; if (commandMap.IsAvailable(RedisCommand.CONFIG)) { if (multiplexer.RawConfig.KeepAlive <= 0) { msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.timeout); msg.SetInternalCall(); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); } msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.slave_read_only); msg.SetInternalCall(); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); msg = Message.Create(-1, flags, RedisCommand.CONFIG, RedisLiterals.GET, RedisLiterals.databases); msg.SetInternalCall(); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); } if (commandMap.IsAvailable(RedisCommand.INFO)) { lastInfoReplicationCheckTicks = Environment.TickCount; if (features.InfoSections) { msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.replication); msg.SetInternalCall(); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); msg = Message.Create(-1, flags, RedisCommand.INFO, RedisLiterals.server); msg.SetInternalCall(); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); } else { msg = Message.Create(-1, flags, RedisCommand.INFO); msg.SetInternalCall(); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); } } else if (commandMap.IsAvailable(RedisCommand.SET)) { // this is a nasty way to find if we are a slave, and it will only work on up-level servers, but... RedisKey key = Guid.NewGuid().ToByteArray(); msg = Message.Create(0, flags, RedisCommand.SET, key, RedisLiterals.slave_read_only, RedisLiterals.PX, 1, RedisLiterals.NX); msg.SetInternalCall(); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.AutoConfigure); } if (commandMap.IsAvailable(RedisCommand.CLUSTER)) { msg = Message.Create(-1, flags, RedisCommand.CLUSTER, RedisLiterals.NODES); msg.SetInternalCall(); WriteDirectOrQueueFireAndForget(connection, msg, ResultProcessor.ClusterNodes); } }
protected override bool SetResultCore(PhysicalConnection connection, Message message, RawResult result) { var tran = message as TransactionMessage; if (tran != null) { var bridge = connection.Bridge; var wrapped = tran.InnerOperations; switch (result.Type) { case ResultType.SimpleString: if (tran.IsAborted && result.IsEqual(RedisLiterals.BytesOK)) { connection.Multiplexer.Trace("Acknowledging UNWATCH (aborted electively)"); SetResult(message, false); return(true); } //EXEC returned with a NULL if (!tran.IsAborted && result.IsNull) { connection.Multiplexer.Trace("Server aborted due to failed EXEC"); //cancel the commands in the transaction and mark them as complete with the completion manager foreach (var op in wrapped) { op.Wrapped.Cancel(); bridge.CompleteSyncOrAsync(op.Wrapped); } SetResult(message, false); return(true); } break; case ResultType.MultiBulk: if (!tran.IsAborted) { var arr = result.GetItems(); if (arr == null) { connection.Multiplexer.Trace("Server aborted due to failed WATCH"); foreach (var op in wrapped) { op.Wrapped.Cancel(); bridge.CompleteSyncOrAsync(op.Wrapped); } SetResult(message, false); return(true); } else if (wrapped.Length == arr.Length) { connection.Multiplexer.Trace("Server committed; processing nested replies"); for (int i = 0; i < arr.Length; i++) { if (wrapped[i].Wrapped.ComputeResult(connection, arr[i])) { bridge.CompleteSyncOrAsync(wrapped[i].Wrapped); } } SetResult(message, true); return(true); } } break; } // even if we didn't fully understand the result, we still need to do something with // the pending tasks foreach (var op in wrapped) { op.Wrapped.Fail(ConnectionFailureType.ProtocolFailure, null); bridge.CompleteSyncOrAsync(op.Wrapped); } } return(false); }
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" }
internal override void WriteImpl(PhysicalConnection physical) { physical.WriteHeader(Command, 1); physical.Write(Key); }
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); }
private bool WriteMessageToServer(PhysicalConnection connection, Message message) { if (message == null) { return(true); } try { var cmd = message.Command; bool isMasterOnly = message.IsMasterOnly(); if (isMasterOnly && ServerEndPoint.IsSlave && (ServerEndPoint.SlaveReadOnly || !ServerEndPoint.AllowSlaveWrites)) { throw ExceptionFactory.MasterOnly(Multiplexer.IncludeDetailInExceptions, message.Command, message, ServerEndPoint); } SelectDatabase(connection, message); if (!connection.TransactionActive) { var readmode = connection.GetReadModeCommand(isMasterOnly); if (readmode != null) { connection.Enqueue(readmode); readmode.WriteTo(connection); readmode.SetRequestSent(); IncrementOpCount(); } if (message.IsAsking) { var asking = ReusableAskingCommand; connection.Enqueue(asking); asking.WriteImpl(connection); asking.SetRequestSent(); IncrementOpCount(); } } switch (cmd) { case RedisCommand.WATCH: case RedisCommand.MULTI: connection.TransactionActive = true; break; case RedisCommand.UNWATCH: case RedisCommand.EXEC: case RedisCommand.DISCARD: connection.TransactionActive = false; break; } connection.Enqueue(message); message.WriteImpl(connection); message.SetRequestSent(); IncrementOpCount(); // some commands smash our ability to trust the database; some commands // demand an immediate flush switch (cmd) { case RedisCommand.EVAL: case RedisCommand.EVALSHA: if (!ServerEndPoint.GetFeatures().ScriptingDatabaseSafe) { connection.SetUnknownDatabase(); } break; case RedisCommand.DISCARD: case RedisCommand.EXEC: connection.SetUnknownDatabase(); break; } return(true); } catch (RedisCommandException ex) { Trace("Write failed: " + ex.Message); message.Fail(ConnectionFailureType.ProtocolFailure, ex); CompleteSyncOrAsync(message); // this failed without actually writing; we're OK with that... unless there's a transaction if (connection != null && connection.TransactionActive) { // we left it in a broken state; need to kill the connection connection.RecordConnectionFailed(ConnectionFailureType.ProtocolFailure, ex); return(false); } return(true); } catch (Exception ex) { Trace("Write failed: " + ex.Message); message.Fail(ConnectionFailureType.InternalFailure, ex); CompleteSyncOrAsync(message); // we're not sure *what* happened here; probably an IOException; kill the connection connection?.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); return(false); } }