private async ValueTask <WriteResult> WriteMessageTakingDelayedWriteLockAsync(ValueTask <LockToken> pendingLock, PhysicalConnection physical, Message message) { try { // WriteMessageTakingWriteLockAsync will have checked for immediate availability, // so this is the fallback case - fine to go straight to "await" // note: timeout is specified in mutex-constructor using (var token = await pendingLock) { if (!token.Success) { message.Cancel(); Multiplexer?.OnMessageFaulted(message, null); this.CompleteSyncOrAsync(message); return(WriteResult.TimeoutBeforeWrite); } var result = WriteMessageInsideLock(physical, message); if (result == WriteResult.Success) { result = await physical.FlushAsync(false).ForAwait(); } physical.SetIdle(); UnmarkActiveMessage(message); return(result); } } catch (Exception ex) { return(HandleWriteException(message, ex)); } }
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)); } }
internal void OnFullyEstablished(PhysicalConnection connection) { Trace("OnFullyEstablished"); connection?.SetIdle(); if (physical == connection && !isDisposed && ChangeState(State.ConnectedEstablishing, State.ConnectedEstablished)) { reportNextFailure = reconfigureNextFailure = true; LastException = null; Interlocked.Exchange(ref failConnectCount, 0); ServerEndPoint.OnFullyEstablished(connection); bool createWorker; lock (_backlog) // do we have pending system things to do? { createWorker = _backlog.Count != 0; } if (createWorker) { StartBacklogProcessor(); } if (ConnectionType == ConnectionType.Interactive) { ServerEndPoint.CheckInfoReplication(); } } else { try { connection.Dispose(); } catch { } } }
private async ValueTask <WriteResult> WriteMessageTakingWriteLockAsync_Awaited(ValueTask <LockToken> pending, PhysicalConnection physical, Message message) { try { using (var token = await pending) { if (!token.Success) { return(TimedOutBeforeWrite(message)); } var result = WriteMessageInsideLock(physical, message); if (result == WriteResult.Success) { result = await physical.FlushAsync(false); } UnmarkActiveMessage(message); physical.SetIdle(); return(result); } } catch (Exception ex) { return(HandleWriteException(message, ex)); } }
private async Task <WriteResult> WriteMessageTakingDelayedWriteLockAsync(PhysicalConnection physical, Message message) { bool haveLock = false; try { // WriteMessageTakingWriteLockAsync will have checked for immediate availability, // so this is the fallback case - fine to go straight to "await" haveLock = await _SingleWriterLock.WaitAsync(TimeoutMilliseconds).ForAwait(); if (!haveLock) { message.Cancel(); Multiplexer?.OnMessageFaulted(message, null); this.CompleteSyncOrAsync(message); return(WriteResult.TimeoutBeforeWrite); } var result = WriteMessageInsideLock(physical, message); if (result == WriteResult.Success) { result = await physical.FlushAsync(false).ForAwait(); } physical.SetIdle(); return(result); } catch (Exception ex) { return(HandleWriteException(message, ex)); } finally { if (haveLock) { ReleaseSingleWriterLock(message); } } }
/// <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(); } }
private async ValueTask <WriteResult> CompleteWriteAndReleaseLockAsync(LockToken lockToken, ValueTask <WriteResult> flush, Message message) { using (lockToken) { try { var result = await flush.ForAwait(); UnmarkActiveMessage(message); physical.SetIdle(); return(result); } catch (Exception ex) { return(HandleWriteException(message, ex)); } } }
/// <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(); } } }
/// <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); } } }
internal void OnFullyEstablished(PhysicalConnection connection) { Trace("OnFullyEstablished"); connection?.SetIdle(); if (physical == connection && !isDisposed && ChangeState(State.ConnectedEstablishing, State.ConnectedEstablished)) { reportNextFailure = reconfigureNextFailure = true; LastException = null; Interlocked.Exchange(ref failConnectCount, 0); ServerEndPoint.OnFullyEstablished(connection); WritePendingBacklog(connection); if (ConnectionType == ConnectionType.Interactive) { ServerEndPoint.CheckInfoReplication(); } } else { try { connection.Dispose(); } catch { } } }
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); } } }
private void ProcessBacklog() { LockToken token = default; try { while (true) { // try and get the lock; if unsuccessful, check for termination token = _singleWriterMutex.TryWait(); if (token) { break; // got the lock } lock (_backlog) { if (_backlog.Count == 0) { return; } } } // so now we are the writer; write some things! Message message; var timeout = TimeoutMilliseconds; while (true) { lock (_backlog) { if (_backlog.Count == 0) { break; // all done } message = _backlog.Dequeue(); } try { if (message.HasAsyncTimedOut(Environment.TickCount, timeout, out var elapsed)) { var ex = Multiplexer.GetException(WriteResult.TimeoutBeforeWrite, message, ServerEndPoint); message.SetExceptionAndComplete(ex, this); } else { var result = WriteMessageInsideLock(physical, message); if (result == WriteResult.Success) { #pragma warning disable CS0618 result = physical.FlushSync(false, timeout); #pragma warning restore CS0618 } UnmarkActiveMessage(message); if (result != WriteResult.Success) { var ex = Multiplexer.GetException(result, message, ServerEndPoint); HandleWriteException(message, ex); } } } catch (Exception ex) { HandleWriteException(message, ex); } } physical.SetIdle(); } 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 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); }
/// <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(); } } }