internal Message GetSelectDatabaseCommand(int targetDatabase, Message message) { if (targetDatabase < 0) { return(null); } if (targetDatabase != currentDatabase) { var serverEndpoint = Bridge.ServerEndPoint; int available = serverEndpoint.Databases; if (!serverEndpoint.HasDatabases) // only db0 is available on cluster/twemproxy { if (targetDatabase != 0) { // should never see this, since the API doesn't allow it; thus not too worried about ExceptionFactory throw new RedisCommandException("Multiple databases are not supported on this server; cannot switch to database: " + targetDatabase); } return(null); } if (message.Command == RedisCommand.SELECT) { // this could come from an EVAL/EVALSHA inside a transaction, for example; we'll accept it Bridge.Trace("Switching database: " + targetDatabase); currentDatabase = targetDatabase; return(null); } if (TransactionActive) {// should never see this, since the API doesn't allow it; thus not too worried about ExceptionFactory throw new RedisCommandException("Multiple databases inside a transaction are not currently supported: " + targetDatabase); } if (available != 0 && targetDatabase >= available) // we positively know it is out of range { throw ExceptionFactory.DatabaseOutfRange(Multiplexer.IncludeDetailInExceptions, targetDatabase, message, serverEndpoint); } Bridge.Trace("Switching database: " + targetDatabase); currentDatabase = targetDatabase; return(GetSelectDatabaseCommand(targetDatabase)); } return(null); }
/// <summary> /// Begins profiling for the given context. /// /// If the same context object is returned by the registered IProfiler, the IProfiledCommands /// will be associated with each other. /// /// Call FinishProfiling with the same context to get the assocated commands. /// /// Note that forContext cannot be a WeakReference or a WeakReference<T> /// </summary> public void BeginProfiling(object forContext) { if (profiler == null) { throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler"); } if (forContext == null) { throw new ArgumentNullException(nameof(forContext)); } if (forContext is WeakReference) { throw new ArgumentException("Context object cannot be a WeakReference", nameof(forContext)); } if (!profiledCommands.TryCreate(forContext)) { throw ExceptionFactory.BeganProfilingWithDuplicateContext(forContext); } }
internal const int REDIS_MAX_ARGS = 1024 * 1024; // there is a <= 1024*1024 max constraint inside redis itself: https://github.com/antirez/redis/blob/6c60526db91e23fb2d666fc52facc9a11780a2a3/src/networking.c#L1024 internal void WriteHeader(string command, int arguments) { if (arguments >= REDIS_MAX_ARGS) // using >= here because we will be adding 1 for the command itself (which is an arg for the purposes of the multi-bulk protocol) { throw ExceptionFactory.TooManyArgs(Multiplexer.IncludeDetailInExceptions, command, null, Bridge.ServerEndPoint, arguments + 1); } var commandBytes = Multiplexer.CommandMap.GetBytes(command); if (commandBytes == null) { throw ExceptionFactory.CommandDisabled(Multiplexer.IncludeDetailInExceptions, command, null, Bridge.ServerEndPoint); } outStream.WriteByte((byte)'*'); // remember the time of the first write that still not followed by read Interlocked.CompareExchange(ref firstUnansweredWriteTickCount, Environment.TickCount, 0); WriteRaw(outStream, arguments + 1); WriteUnified(outStream, commandBytes); }
protected Message(int db, CommandFlags flags, RedisCommand command) { bool dbNeeded = Message.RequiresDatabase(command); if (db < 0) { if (dbNeeded) { throw ExceptionFactory.DatabaseRequired(false, command); } } else { if (!dbNeeded) { throw ExceptionFactory.DatabaseNotRequired(false, command); } } if (IsMasterOnly(command)) { switch (GetMasterSlaveFlags(flags)) { case CommandFlags.DemandSlave: throw ExceptionFactory.MasterOnly(false, command, null, null); case CommandFlags.DemandMaster: // already fine as-is break; case CommandFlags.PreferMaster: case CommandFlags.PreferSlave: default: // we will run this on the master, then flags = SetMasterSlaveFlags(flags, CommandFlags.DemandMaster); break; } } this.Db = db; this.command = command; this.flags = flags & UserSelectableFlags; }
public ServerEndPoint Select(Message message) { if (message == null) { throw new ArgumentNullException("message"); } int slot = NoSlot; switch (serverType) { case ServerType.Cluster: case ServerType.Twemproxy: // strictly speaking twemproxy uses a different hashing algo, but the hash-tag behavior is // the same, so this does a pretty good job of spotting illegal commands before sending them slot = message.GetHashSlot(this); if (slot == MultipleSlots) { throw ExceptionFactory.MultiSlot(multiplexer.IncludeDetailInExceptions, message); } break; } return(Select(slot, message.Command, message.Flags)); }
internal override Task <T> ExecuteAsync <T>(Message message, ResultProcessor <T> processor, ServerEndPoint server = null) { // inject our expected server automatically if (server == null) { server = this.server; } FixFlags(message, server); if (!server.IsConnected) { if (message == null) { return(CompletedTask <T> .Default(asyncState)); } if (message.IsFireAndForget) { return(CompletedTask <T> .Default(null)); // F+F explicitly does not get async-state } // no need to deny exec-sync here; will be complete before they see if var tcs = TaskSource.Create <T>(asyncState); ConnectionMultiplexer.ThrowFailed(tcs, ExceptionFactory.NoConnectionAvailable(multiplexer.IncludeDetailInExceptions, message.Command, message, server)); return(tcs.Task); } return(base.ExecuteAsync <T>(message, processor, server)); }
private RawResult ReadArray(byte[] buffer, ref int offset, ref int count) { var itemCount = ReadLineTerminatedString(ResultType.Integer, buffer, ref offset, ref count); if (itemCount.HasValue) { long i64; if (!itemCount.TryGetInt64(out i64)) { throw ExceptionFactory.ConnectionFailure(Multiplexer.IncludeDetailInExceptions, ConnectionFailureType.ProtocolFailure, "Invalid array length", Bridge.ServerEndPoint); } int itemCountActual = checked ((int)i64); if (itemCountActual < 0) { //for null response by command like EXEC, RESP array: *-1\r\n return(new RawResult(ResultType.SimpleString, null, 0, 0)); } else if (itemCountActual == 0) { //for zero array response by command like SCAN, Resp array: *0\r\n return(RawResult.EmptyArray); } var arr = new RawResult[itemCountActual]; for (int i = 0; i < itemCountActual; i++) { if (!(arr[i] = TryParseResult(buffer, ref offset, ref count)).HasValue) { return(RawResult.Nil); } } return(new RawResult(arr)); } return(RawResult.Nil); }
/// <summary> /// Stops profiling for the given context, returns all IProfiledCommands associated. /// /// By default this may do a sweep for dead profiling contexts, you can disable this by passing "allowCleanupSweep: false". /// </summary> /// <param name="forContext">The context to begin profiling.</param> /// <param name="allowCleanupSweep">Whether to allow cleanup of old profiling sessions.</param> public ProfiledCommandEnumerable FinishProfiling(object forContext, bool allowCleanupSweep = true) { if (profiler == null) { throw new InvalidOperationException("Cannot begin profiling if no IProfiler has been registered with RegisterProfiler"); } if (forContext == null) { throw new ArgumentNullException(nameof(forContext)); } if (!profiledCommands.TryRemove(forContext, out ProfiledCommandEnumerable ret)) { throw ExceptionFactory.FinishedProfilingWithInvalidContext(forContext); } // conditional, because it could hurt and that may sometimes be unacceptable if (allowCleanupSweep) { profiledCommands.TryCleanup(); } return(ret); }
internal void OnHeartbeat(bool ifConnectedOnly) { bool runThisTime = false; try { runThisTime = !isDisposed && Interlocked.CompareExchange(ref beating, 1, 0) == 0; if (!runThisTime) { return; } uint index = (uint)Interlocked.Increment(ref profileLogIndex); long newSampleCount = Interlocked.Read(ref operationCount); Interlocked.Exchange(ref profileLog[index % ProfileLogSamples], newSampleCount); Interlocked.Exchange(ref profileLastLog, newSampleCount); Trace("OnHeartbeat: " + (State)state); switch (state) { case (int)State.Connecting: int connectTimeMilliseconds = unchecked (Environment.TickCount - Thread.VolatileRead(ref connectStartTicks)); bool shouldRetry = Multiplexer.RawConfig.ReconnectRetryPolicy.ShouldRetry(Interlocked.Read(ref connectTimeoutRetryCount), connectTimeMilliseconds); if (shouldRetry) { Interlocked.Increment(ref connectTimeoutRetryCount); LastException = ExceptionFactory.UnableToConnect(Multiplexer, "ConnectTimeout"); Trace("Aborting connect"); // abort and reconnect var snapshot = physical; OnDisconnected(ConnectionFailureType.UnableToConnect, snapshot, out bool isCurrent, out State oldState); using (snapshot) { } // dispose etc TryConnect(null); } break; case (int)State.ConnectedEstablishing: case (int)State.ConnectedEstablished: var tmp = physical; if (tmp != null) { if (state == (int)State.ConnectedEstablished) { Interlocked.Exchange(ref connectTimeoutRetryCount, 0); tmp.BridgeCouldBeNull?.ServerEndPoint?.ClearUnselectable(UnselectableFlags.DidNotRespond); } tmp.OnBridgeHeartbeat(); int writeEverySeconds = ServerEndPoint.WriteEverySeconds, checkConfigSeconds = Multiplexer.RawConfig.ConfigCheckSeconds; if (state == (int)State.ConnectedEstablished && ConnectionType == ConnectionType.Interactive && checkConfigSeconds > 0 && ServerEndPoint.LastInfoReplicationCheckSecondsAgo >= checkConfigSeconds && ServerEndPoint.CheckInfoReplication()) { // that serves as a keep-alive, if it is accepted } else if (writeEverySeconds > 0 && tmp.LastWriteSecondsAgo >= writeEverySeconds) { Trace("OnHeartbeat - overdue"); if (state == (int)State.ConnectedEstablished) { KeepAlive(); } else { OnDisconnected(ConnectionFailureType.SocketFailure, tmp, out bool ignore, out State oldState); } } else if (writeEverySeconds <= 0 && tmp.IsIdle() && tmp.LastWriteSecondsAgo > 2 && tmp.GetSentAwaitingResponseCount() != 0) { // there's a chance this is a dead socket; sending data will shake that // up a bit, so if we have an empty unsent queue and a non-empty sent // queue, test the socket KeepAlive(); } } break; case (int)State.Disconnected: Interlocked.Exchange(ref connectTimeoutRetryCount, 0); if (!ifConnectedOnly) { Multiplexer.Trace("Resurrecting " + ToString()); Multiplexer.OnResurrecting(ServerEndPoint?.EndPoint, ConnectionType); GetConnection(null); } break; default: Interlocked.Exchange(ref connectTimeoutRetryCount, 0); break; } } catch (Exception ex) { OnInternalError(ex); Trace("OnHeartbeat error: " + ex.Message); } finally { if (runThisTime) { Interlocked.Exchange(ref beating, 0); } } }
private WriteResult WriteMessageToServerInsideWriteLock(PhysicalConnection connection, Message message) { if (message == null) { return(WriteResult.Success); // for some definition of success } bool isQueued = false; try { var cmd = message.Command; LastCommand = cmd; bool isMasterOnly = message.IsMasterOnly(); if (isMasterOnly && ServerEndPoint.IsSlave && (ServerEndPoint.SlaveReadOnly || !ServerEndPoint.AllowSlaveWrites)) { throw ExceptionFactory.MasterOnly(Multiplexer.IncludeDetailInExceptions, message.Command, message, ServerEndPoint); } switch (cmd) { case RedisCommand.QUIT: connection.RecordQuit(); break; case RedisCommand.EXEC: Multiplexer.OnPreTransactionExec(message); // testing purposes, to force certain errors break; } SelectDatabaseInsideWriteLock(connection, message); if (!connection.TransactionActive) { var readmode = connection.GetReadModeCommand(isMasterOnly); if (readmode != null) { connection.EnqueueInsideWriteLock(readmode); readmode.WriteTo(connection); readmode.SetRequestSent(); IncrementOpCount(); } if (message.IsAsking) { var asking = ReusableAskingCommand; connection.EnqueueInsideWriteLock(asking); asking.WriteTo(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.EnqueueInsideWriteLock(message); isQueued = true; message.WriteTo(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.UNKNOWN: case RedisCommand.DISCARD: case RedisCommand.EXEC: connection.SetUnknownDatabase(); break; } return(WriteResult.Success); } catch (RedisCommandException ex) when(!isQueued) { Trace("Write failed: " + ex.Message); message.Fail(ConnectionFailureType.InternalFailure, ex, null); this.CompleteSyncOrAsync(message); // this failed without actually writing; we're OK with that... unless there's a transaction if (connection?.TransactionActive == true) { // we left it in a broken state; need to kill the connection connection.RecordConnectionFailed(ConnectionFailureType.ProtocolFailure, ex); return(WriteResult.WriteFailure); } return(WriteResult.Success); } catch (Exception ex) { Trace("Write failed: " + ex.Message); message.Fail(ConnectionFailureType.InternalFailure, ex, null); this.CompleteSyncOrAsync(message); // we're not sure *what* happened here; probably an IOException; kill the connection connection?.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); return(WriteResult.WriteFailure); } }
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 if (connection != null) { connection.RecordConnectionFailed(ConnectionFailureType.InternalFailure, ex); } return(false); } }