예제 #1
0
        public bool TryEnqueue(Message message, bool isSlave)
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException(Name);
            }
            if (!IsConnected)
            {
                if (message.IsInternalCall)
                {
                    // you can go in the queue, but we won't be starting
                    // a worker, because the handshake has not completed
                    queue.Push(message);
                    message.SetEnqueued();
                    return(true);
                }
                else
                {
                    // sorry, we're just not ready for you yet;
                    return(false);
                }
            }

            bool reqWrite = queue.Push(message);

            message.SetEnqueued();
            LogNonPreferred(message.Flags, isSlave);
            Trace("Now pending: " + GetPendingCount());

            if (reqWrite)
            {
                Multiplexer.RequestWrite(this, false);
            }
            return(true);
        }
예제 #2
0
        internal WriteResult WriteMessageTakingWriteLockSync(PhysicalConnection physical, Message message)
        {
            Trace("Writing: " + message);
            message.SetEnqueued(physical); // this also records the read/write stats at this point

            try
            {
                using (var token = _singleWriterMutex.TryWait())
                {
                    if (!token.Success)
                    {
                        message.Cancel();
                        Multiplexer?.OnMessageFaulted(message, null);
                        this.CompleteSyncOrAsync(message);
                        return(WriteResult.TimeoutBeforeWrite);
                    }

                    var result = WriteMessageInsideLock(physical, message);

                    if (result == WriteResult.Success)
                    {
#pragma warning disable CS0618
                        result = physical.FlushSync(false, TimeoutMilliseconds);
#pragma warning restore CS0618
                    }

                    UnmarkActiveMessage(message);
                    physical.SetIdle();
                    return(result);
                }
            }
            catch (Exception ex) { return(HandleWriteException(message, ex)); }
        }
예제 #3
0
        /// <summary>
        /// This writes a message to the output stream
        /// </summary>
        /// <param name="physical">The phsyical connection to write to.</param>
        /// <param name="message">The message to be written.</param>
        internal ValueTask <WriteResult> WriteMessageTakingWriteLockAsync(PhysicalConnection physical, Message message)
        {
            Trace("Writing: " + message);
            message.SetEnqueued(physical); // this also records the read/write stats at this point

            bool      releaseLock = false;
            LockToken token       = default;

            try
            {
                // try to acquire it synchronously
                // note: timeout is specified in mutex-constructor
                var pending = _singleWriterMutex.TryWaitAsync(options: MutexSlim.WaitOptions.DisableAsyncContext);
                if (!pending.IsCompletedSuccessfully)
                {
                    return(WriteMessageTakingDelayedWriteLockAsync(pending, physical, message));
                }

                releaseLock = true;
                token       = pending.Result; // we can't use "using" for this, because we might not want to kill it yet
                if (!token.Success)           // (in particular, me might hand the lifetime to CompleteWriteAndReleaseLockAsync)
                {
                    message.Cancel();
                    Multiplexer?.OnMessageFaulted(message, null);
                    this.CompleteSyncOrAsync(message);
                    return(new ValueTask <WriteResult>(WriteResult.TimeoutBeforeWrite));
                }

                var result = WriteMessageInsideLock(physical, message);

                if (result == WriteResult.Success)
                {
                    var flush = physical.FlushAsync(false);
                    if (!flush.IsCompletedSuccessfully)
                    {
                        releaseLock = false; // so we don't release prematurely
                        return(CompleteWriteAndReleaseLockAsync(token, flush, message));
                    }

                    result = flush.Result; // we know it was completed, this is fine
                }

                UnmarkActiveMessage(message);
                physical.SetIdle();

                return(new ValueTask <WriteResult>(result));
            }
            catch (Exception ex) { return(new ValueTask <WriteResult>(HandleWriteException(message, ex))); }
            finally
            {
                if (releaseLock)
                {
                    token.Dispose();
                }
            }
        }
        internal WriteResult WriteMessageTakingWriteLockSync(PhysicalConnection physical, Message message)
        {
            Trace("Writing: " + message);
            message.SetEnqueued(physical); // this also records the read/write stats at this point

            LockToken token = default;

            try
            {
                token = _singleWriterMutex.TryWait(WaitOptions.NoDelay);
                if (!token.Success)
                {
                    // we can't get it *instantaneously*; is there
                    // perhaps a backlog and active backlog processor?
                    bool haveBacklog;
                    lock (_backlog)
                    {
                        haveBacklog = _backlog.Count != 0;
                    }
                    if (haveBacklog)
                    {
                        PushToBacklog(message);
                        return(WriteResult.Success); // queued counts as success
                    }

                    // no backlog... try to wait with the timeout;
                    // if we *still* can't get it: that counts as
                    // an actual timeout
                    token = _singleWriterMutex.TryWait();
                    if (!token.Success)
                    {
                        message.Cancel();
                        Multiplexer?.OnMessageFaulted(message, null);
                        message.Complete();
                        return(WriteResult.TimeoutBeforeWrite);
                    }
                }

                var result = WriteMessageInsideLock(physical, message);

                if (result == WriteResult.Success)
                {
#pragma warning disable CS0618
                    result = physical.FlushSync(false, TimeoutMilliseconds);
#pragma warning restore CS0618
                }

                UnmarkActiveMessage(message);
                physical.SetIdle();
                return(result);
            }
            catch (Exception ex) { return(HandleWriteException(message, ex)); }
            finally { token.Dispose(); }
        }
        /// <summary>
        /// This writes a message to the output stream
        /// </summary>
        /// <param name="physical">The phsyical connection to write to.</param>
        /// <param name="message">The message to be written.</param>
        internal ValueTask <WriteResult> WriteMessageTakingWriteLockAsync(PhysicalConnection physical, Message message)
        {
            Trace("Writing: " + message);
            message.SetEnqueued(physical); // this also records the read/write stats at this point

            bool      releaseLock = true;
            LockToken token       = default;

            try
            {
                // try to acquire it synchronously
                // note: timeout is specified in mutex-constructor
                token = _singleWriterMutex.TryWait(options: WaitOptions.NoDelay);

                if (!token.Success) // (in particular, me might hand the lifetime to CompleteWriteAndReleaseLockAsync)
                {
                    PushToBacklog(message);
                    return(new ValueTask <WriteResult>(WriteResult.Success)); // queued counts as success
                }

                var result = WriteMessageInsideLock(physical, message);

                if (result == WriteResult.Success)
                {
                    var flush = physical.FlushAsync(false);
                    if (!flush.IsCompletedSuccessfully)
                    {
                        releaseLock = false; // so we don't release prematurely
                        return(CompleteWriteAndReleaseLockAsync(token, flush, message));
                    }

                    result = flush.Result; // we know it was completed, this is fine
                }

                UnmarkActiveMessage(message);
                physical.SetIdle();

                return(new ValueTask <WriteResult>(result));
            }
            catch (Exception ex) { return(new ValueTask <WriteResult>(HandleWriteException(message, ex))); }
            finally
            {
                if (releaseLock)
                {
                    token.Dispose();
                }
            }
        }
예제 #6
0
        public WriteResult TryWrite(Message message, bool isSlave)
        {
            if (isDisposed)
            {
                throw new ObjectDisposedException(Name);
            }
            if (!IsConnected)
            {
                if (message.IsInternalCall && message.Command != RedisCommand.QUIT)
                {
                    // you can go in the queue, but we won't be starting
                    // a worker, because the handshake has not completed
                    var queue = _preconnectBacklog;
                    lock (queue)
                    {
                        queue.Enqueue(message);
                    }
                    message.SetEnqueued();
                    return(WriteResult.Success); // we'll take it...
                }
                else
                {
                    // sorry, we're just not ready for you yet;
                    message.Cancel();
                    Multiplexer?.OnMessageFaulted(message, null);
                    this.CompleteSyncOrAsync(message);
                    return(WriteResult.NoConnectionAvailable);
                }
            }

            var physical = this.physical;

            if (physical == null)
            {
                message.Cancel();
                Multiplexer?.OnMessageFaulted(message, null);
                this.CompleteSyncOrAsync(message);
                return(WriteResult.NoConnectionAvailable);
            }

            var result = WriteMessageTakingWriteLock(physical, message);

            LogNonPreferred(message.Flags, isSlave);

            return(result);
        }
예제 #7
0
        /// <summary>
        /// This writes a message to the output stream
        /// </summary>
        /// <param name="physical">The phsyical connection to write to.</param>
        /// <param name="message">The message to be written.</param>
        internal ValueTask <WriteResult> WriteMessageTakingWriteLockAsync(PhysicalConnection physical, Message message)
        {
            Trace("Writing: " + message);
            message.SetEnqueued(physical); // this also records the read/write stats at this point

            bool haveLock = false;

            try
            {
                // try to acquire it synchronously
                haveLock = _SingleWriterLock.Wait(0);
                if (!haveLock)
                {
                    return(new ValueTask <WriteResult>(WriteMessageTakingDelayedWriteLockAsync(physical, message)));
                }

                var result = WriteMessageInsideLock(physical, message);

                if (result == WriteResult.Success)
                {
                    var flush = physical.FlushAsync(false);
                    if (!flush.IsCompletedSuccessfully)
                    {
                        haveLock = false; // so we don't release prematurely
                        return(new ValueTask <WriteResult>(CompleteWriteAndReleaseLockAsync(flush, message)));
                    }

                    result = flush.Result; // we know it was completed, this is fine
                }
                physical.SetIdle();

                return(new ValueTask <WriteResult>(result));
            }
            catch (Exception ex) { return(new ValueTask <WriteResult>(HandleWriteException(message, ex))); }
            finally { if (haveLock)
                      {
                          ReleaseSingleWriterLock(message);
                      }
            }
        }
 private WriteResult QueueOrFailMessage(Message message)
 {
     if (message.IsInternalCall && message.Command != RedisCommand.QUIT)
     {
         // you can go in the queue, but we won't be starting
         // a worker, because the handshake has not completed
         lock (_backlog)
         {
             _backlog.Enqueue(message);
         }
         message.SetEnqueued(null);
         return(WriteResult.Success); // we'll take it...
     }
     else
     {
         // sorry, we're just not ready for you yet;
         message.Cancel();
         Multiplexer?.OnMessageFaulted(message, null);
         message.Complete();
         return(WriteResult.NoConnectionAvailable);
     }
 }
예제 #9
0
        internal WriteResult WriteMessageTakingWriteLockSync(PhysicalConnection physical, Message message)
        {
            Trace("Writing: " + message);
            message.SetEnqueued(physical); // this also records the read/write stats at this point

            bool haveLock = false;

            try
            {
                haveLock = _SingleWriterLock.Wait(TimeoutMilliseconds);
                if (!haveLock)
                {
                    message.Cancel();
                    Multiplexer?.OnMessageFaulted(message, null);
                    this.CompleteSyncOrAsync(message);
                    return(WriteResult.TimeoutBeforeWrite);
                }

                var result = WriteMessageInsideLock(physical, message);

                if (result == WriteResult.Success)
                {
#pragma warning disable CS0618
                    result = physical.FlushSync(false, TimeoutMilliseconds);
#pragma warning restore CS0618
                }

                physical.SetIdle();
                return(result);
            }
            catch (Exception ex) { return(HandleWriteException(message, ex)); }
            finally { if (haveLock)
                      {
                          ReleaseSingleWriterLock(message);
                      }
            }
        }
예제 #10
0
        /// <summary>
        /// This writes a message to the output stream
        /// </summary>
        /// <param name="physical">The phsyical connection to write to.</param>
        /// <param name="message">The message to be written.</param>
        internal WriteResult WriteMessageTakingWriteLock(PhysicalConnection physical, Message message)
        {
            Trace("Writing: " + message);
            message.SetEnqueued();

            WriteResult result;
            bool        haveLock = false;

            try
            {
                Monitor.TryEnter(SingleWriterLock, TimeoutMilliseconds, ref haveLock);
                if (!haveLock)
                {
                    message.Cancel();
                    Multiplexer?.OnMessageFaulted(message, null);
                    this.CompleteSyncOrAsync(message);
                    return(WriteResult.TimeoutBeforeWrite);
                }

                var existingMessage = Interlocked.CompareExchange(ref _activeMesssage, message, null);
                if (existingMessage != null)
                {
                    Multiplexer?.OnInfoMessage($"reentrant call to WriteMessageTakingWriteLock for {message.CommandAndKey}, {existingMessage.CommandAndKey} is still active");
                    return(WriteResult.NoConnectionAvailable);
                }
                physical.SetWriting();
                var messageIsSent = false;
                if (message is IMultiMessage)
                {
                    SelectDatabaseInsideWriteLock(physical, message); // need to switch database *before* the transaction
                    foreach (var subCommand in ((IMultiMessage)message).GetMessages(physical))
                    {
                        result = WriteMessageToServerInsideWriteLock(physical, subCommand);
                        if (result != WriteResult.Success)
                        {
                            // we screwed up; abort; note that WriteMessageToServer already
                            // killed the underlying connection
                            Trace("Unable to write to server");
                            message.Fail(ConnectionFailureType.ProtocolFailure, null, "failure before write: " + result.ToString());
                            this.CompleteSyncOrAsync(message);
                            return(result);
                        }
                        //The parent message (next) may be returned from GetMessages
                        //and should not be marked as sent again below
                        messageIsSent = messageIsSent || subCommand == message;
                    }
                    if (!messageIsSent)
                    {
                        message.SetRequestSent(); // well, it was attempted, at least...
                    }

                    result = WriteResult.Success;
                }
                else
                {
                    result = WriteMessageToServerInsideWriteLock(physical, message);
                }

                if (result == WriteResult.Success)
                {
                    result = physical.FlushSync();
                }
                physical.SetIdle();
            }
            catch (Exception ex)
            {
                var inner = new RedisConnectionException(ConnectionFailureType.InternalFailure, "Failed to write", ex);
                message.SetExceptionAndComplete(inner, this);
                result = WriteResult.WriteFailure;
            }
            finally
            {
                if (haveLock)
                {
                    Interlocked.CompareExchange(ref _activeMesssage, null, message); // remove if it is us
                    Monitor.Exit(SingleWriterLock);
                }
            }

            return(result);
        }
예제 #11
0
        /// <summary>
        /// This writes a message to the output stream
        /// </summary>
        /// <param name="physical">The phsyical connection to write to.</param>
        /// <param name="message">The message to be written.</param>
        internal ValueTask <WriteResult> WriteMessageTakingWriteLockAsync(PhysicalConnection physical, Message message)
        {
            /* design decision/choice; the code works fine either way, but if this is
             * set to *true*, then when we can't take the writer-lock *right away*,
             * we push the message to the backlog (starting a worker if needed)
             *
             * otherwise, we go for a TryWaitAsync and rely on the await machinery
             *
             * "true" seems to give faster times *when under heavy contention*, based on profiling
             * but it involves the backlog concept; "false" works well under low contention, and
             * makes more use of async
             */
            const bool ALWAYS_USE_BACKLOG_IF_CANNOT_GET_SYNC_LOCK = true;

            Trace("Writing: " + message);
            message.SetEnqueued(physical); // this also records the read/write stats at this point

            bool      releaseLock = true;  // fine to default to true, as it doesn't matter until token is a "success"
            LockToken token       = default;

            try
            {
                // try to acquire it synchronously
                // note: timeout is specified in mutex-constructor
                token = _singleWriterMutex.TryWait(options: WaitOptions.NoDelay);

                if (!token.Success)
                {
                    // we can't get it *instantaneously*; is there
                    // perhaps a backlog and active backlog processor?
                    if (PushToBacklog(message, onlyIfExists: !ALWAYS_USE_BACKLOG_IF_CANNOT_GET_SYNC_LOCK))
                    {
                        return(new ValueTask <WriteResult>(WriteResult.Success)); // queued counts as success
                    }
                    // no backlog... try to wait with the timeout;
                    // if we *still* can't get it: that counts as
                    // an actual timeout
                    var pending = _singleWriterMutex.TryWaitAsync(options: WaitOptions.DisableAsyncContext);
                    if (!pending.IsCompletedSuccessfully)
                    {
                        return(WriteMessageTakingWriteLockAsync_Awaited(pending, physical, message));
                    }

                    token = pending.Result; // fine since we know we got a result
                    if (!token.Success)
                    {
                        return(new ValueTask <WriteResult>(TimedOutBeforeWrite(message)));
                    }
                }

                var result = WriteMessageInsideLock(physical, message);

                if (result == WriteResult.Success)
                {
                    var flush = physical.FlushAsync(false);
                    if (!flush.IsCompletedSuccessfully)
                    {
                        releaseLock = false; // so we don't release prematurely
                        return(CompleteWriteAndReleaseLockAsync(token, flush, message));
                    }

                    result = flush.Result; // we know it was completed, this is fine
                }

                UnmarkActiveMessage(message);
                physical.SetIdle();

                return(new ValueTask <WriteResult>(result));
            }
            catch (Exception ex) { return(new ValueTask <WriteResult>(HandleWriteException(message, ex))); }
            finally
            {
                if (releaseLock)
                {
                    token.Dispose();
                }
            }
        }