/// <summary> /// Removes all pending write operations, and fail them with the given <see cref="Exception"/>. The messages /// will be released via <see cref="ReferenceCountUtil.SafeRelease(object)"/>. /// </summary> /// <param name="cause">The <see cref="Exception"/> to fail with.</param> public void RemoveAndFailAll(Exception cause) { Debug.Assert(_ctx.Executor.InEventLoop); if (cause is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.cause); } // It is possible for some of the failed promises to trigger more writes. The new writes // will "revive" the queue, so we need to clean them up until the queue is empty. for (PendingWrite write = _head; write is object; write = _head) { _head = _tail = null; _size = 0; _bytes = 0; while (write is object) { PendingWrite next = write.Next; ReferenceCountUtil.SafeRelease(write.Msg); IPromise promise = write.Promise; Recycle(write, false); Util.SafeSetFailure(promise, cause, Logger); write = next; } } AssertEmpty(); }
/** * Add the given {@code msg} and {@link ChannelPromise}. */ public Task Add(object msg) { Contract.Assert(this.ctx.Executor.InEventLoop); Contract.Requires(msg != null); int messageSize = this.estimatorHandle.Size(msg); if (messageSize < 0) { // Size may be unknow so just use 0 messageSize = 0; } var promise = new TaskCompletionSource(); PendingWrite write = PendingWrite.NewInstance(msg, messageSize, promise); PendingWrite currentTail = this.tail; if (currentTail == null) { this.tail = this.head = write; } else { currentTail.Next = write; this.tail = write; } this.size++; // We need to guard against null as channel.Unsafe.OutboundBuffer may returned null // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 this.buffer?.IncrementPendingOutboundBytes(write.Size); return(promise.Task); }
void Recycle(PendingWrite write, bool update) { PendingWrite next = write.Next; long writeSize = write.Size; if (update) { if (next == null) { // Handled last PendingWrite so rest head and tail // Guard against re-entrance by directly reset this.head = this.tail = null; this.size = 0; } else { this.head = next; this.size--; Contract.Assert(this.size > 0); } } write.Recycle(); // We need to guard against null as channel.unsafe().outboundBuffer() may returned null // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 this.buffer?.DecrementPendingOutboundBytes(writeSize); }
public override Task WriteAsync(IChannelHandlerContext context, object message) { var pendingWrite = new PendingWrite(message); this.queue.Enqueue(pendingWrite); return(pendingWrite.PendingTask); }
/// <summary> /// Adds the given message to this <see cref="PendingWriteQueue"/>. /// </summary> /// <param name="msg">The message to add to the <see cref="PendingWriteQueue"/>.</param> /// <param name="promise"></param> public void Add(object msg, IPromise promise) { Debug.Assert(_ctx.Executor.InEventLoop); if (msg is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.msg); } if (promise is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.promise); } // It is possible for writes to be triggered from removeAndFailAll(). To preserve ordering, // we should add them to the queue and let removeAndFailAll() fail them later. int messageSize = GetSize(msg); PendingWrite write = PendingWrite.NewInstance(msg, messageSize, promise); PendingWrite currentTail = _tail; if (currentTail is null) { _tail = _head = write; } else { currentTail.Next = write; _tail = write; } _size++; _bytes += messageSize; _tracker.IncrementPendingOutboundBytes(write.Size); }
void Discard(Exception cause = null) { for (;;) { PendingWrite current = this.currentWrite; if (this.currentWrite == null) { current = this.queue.Count > 0 ? this.queue.Dequeue() : null; } else { this.currentWrite = null; } if (current == null) { break; } object message = current.Message; var chunks = message as IChunkedInput <T>; if (chunks != null) { try { if (!chunks.IsEndOfInput) { if (cause == null) { cause = new ClosedChannelException(); } current.Fail(cause); } else { current.Success(); } } catch (Exception exception) { current.Fail(exception); Logger.Warn($"{StringUtil.SimpleClassName(typeof(ChunkedWriteHandler<T>))}.IsEndOfInput failed", exception); } finally { CloseInput(chunks); } } else { if (cause == null) { cause = new ClosedChannelException(); } current.Fail(cause); } } }
public void Recycle() { this.Size = 0; this.Next = null; this.Msg = null; this.Promise = null; this.handle.Release(this); }
public void Recycle() { this.Size = 0; this.Next = null; this.Messages.Clear(); this.Promise = null; this.handle.Release(this); }
public static PendingWrite NewInstance(object msg, int size, TaskCompletionSource promise) { PendingWrite write = Pool.Take(); write.Add(msg, size); write.Promise = promise; return(write); }
/** connection is closing but a write has to be finished first */ private Receive ClosingWithPendingWrite(ConnectionInfo info, IActorRef closeCommandor, Tcp.ConnectionClosed closedEvent) { return(message => { if (message is Tcp.SuspendReading) { SuspendReading(info); return true; } if (message is Tcp.ResumeReading) { ResumeReading(info); return true; } if (message is SelectionHandler.ChannelReadable) { DoRead(info, closeCommandor); return true; } if (message is SelectionHandler.ChannelWritable) { DoWrite(info); if (!WritePending()) // writing is now finished { HandleClose(info, closeCommandor, closedEvent); } return true; } var updatePendingWrite = message as UpdatePendingWriteAndThen; if (updatePendingWrite != null) { _pendingWrite = updatePendingWrite.RemainingWrite; updatePendingWrite.Work(); if (WritePending()) { info.Registration.EnableInterest(SocketAsyncOperation.Send); } else { HandleClose(info, closeCommandor, closedEvent); } return true; } var writeFailed = message as WriteFileFailed; if (writeFailed != null) { HandleError(info.Handler, writeFailed.E); // rethrow exception from dispatcher task return true; } if (message is Tcp.Abort) { HandleClose(info, Sender, IO.Tcp.Aborted.Instance); return true; } return false; }); }
/// <summary>Add the given <c>msg</c> and returns <see cref="Task" /> for completion of processing <c>msg</c>.</summary> public void Add(object msg, IPromise promise) { Debug.Assert(_ctx.Executor.InEventLoop); if (msg is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.msg); } int messageSize = _estimatorHandle.Size(msg); if (messageSize < 0) { // Size may be unknow so just use 0 messageSize = 0; } PendingWrite currentTail = _tail; if (currentTail is object) { bool canBundle = CanBatch(msg, messageSize, currentTail.Size); if (canBundle) { currentTail.Add(msg, messageSize); if (!promise.IsVoid) { currentTail.Promise.Task.LinkOutcome(promise); } return; } } PendingWrite write; if (promise.IsVoid || promise is SimplePromiseAggregator) { var headPromise = _ctx.NewPromise(); headPromise.Task.LinkOutcome(promise); write = PendingWrite.NewInstance(msg, messageSize, headPromise); } else { write = PendingWrite.NewInstance(msg, messageSize, promise); } if (currentTail is null) { _tail = _head = write; } else { currentTail.Next = write; _tail = write; } _size++; // We need to guard against null as channel.Unsafe.OutboundBuffer may returned null // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 _buffer?.IncrementPendingOutboundBytes(messageSize); }
/// <summary> /// Removes a pending write operation and release it's message via {@link ReferenceCountUtil#safeRelease(Object)}. /// </summary> /// <returns><seealso cref="TaskCompletionSource" /> of the pending write or <c>null</c> if the queue is empty.</returns> public TaskCompletionSource Remove() { Contract.Assert(this.ctx.Executor.InEventLoop); PendingWrite write = this.head; if (write == null) { return(null); } TaskCompletionSource promise = write.Promise; ReferenceCountUtil.SafeRelease(write.Msg); this.Recycle(write, true); return(promise); }
/** * Remove a pending write operation and fail it with the given {@link Throwable}. The message will be released via * {@link ReferenceCountUtil#safeRelease(Object)}. */ public void RemoveAndFail(Exception cause) { Contract.Assert(this.ctx.Executor.InEventLoop); Contract.Requires(cause != null); PendingWrite write = this.head; if (write == null) { return; } ReferenceCountUtil.SafeRelease(write.Msg); TaskCompletionSource promise = write.Promise; Util.SafeSetFailure(promise, cause, Logger); this.Recycle(write, true); }
/** * Removes a pending write operation and performs it via * {@link ChannelHandlerContext#write(Object, ChannelPromise)}. * * @return {@link ChannelFuture} if something was written and {@code null} * if the {@link PendingWriteQueue} is empty. */ public Task RemoveAndWriteAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); PendingWrite write = this.head; if (write == null) { return(null); } object msg = write.Msg; TaskCompletionSource promise = write.Promise; this.Recycle(write, true); this.ctx.WriteAsync(msg).LinkOutcome(promise); return(promise.Task); }
/// <summary> /// Connection is closing but a write has to be finished first /// </summary> private Receive ClosingWithPendingWrite(ConnectionInfo info, IActorRef closeCommander, ConnectionClosed closedEvent) { return(message => { switch (message) { case SuspendReading _: SuspendReading(); return true; case ResumeReading _: ResumeReading(); return true; case SocketReceived _: DoRead(info, closeCommander); return true; case SocketSent _: AcknowledgeSent(); if (IsWritePending) { DoWrite(info); } else { HandleClose(info, closeCommander, closedEvent); } return true; case UpdatePendingWriteAndThen updatePendingWrite: pendingWrite = updatePendingWrite.RemainingWrite; updatePendingWrite.Work(); if (IsWritePending) { DoWrite(info); } else { HandleClose(info, closeCommander, closedEvent); } return true; case WriteFileFailed fail: HandleError(info.Handler, fail.Cause); return true; case Abort _: HandleClose(info, Sender, Aborted.Instance); return true; default: return false; } }); }
/// <summary> /// Removes all pending write operation and performs them via <see cref="IChannelHandlerContext.WriteAsync(object, IPromise)"/> /// </summary> /// <returns><see cref="Task"/> if something was written and <c>null</c> /// if the <see cref="PendingWriteQueue"/> is empty.</returns> public Task RemoveAndWriteAllAsync() { Debug.Assert(_ctx.Executor.InEventLoop); if (IsEmpty) { return(null); } var p = _ctx.NewPromise(); PromiseCombiner combiner = new PromiseCombiner(_ctx.Executor); try { // It is possible for some of the written promises to trigger more writes. The new writes // will "revive" the queue, so we need to write them up until the queue is empty. for (PendingWrite write = _head; write is object; write = _head) { _head = _tail = null; _size = 0; _bytes = 0; while (write is object) { PendingWrite next = write.Next; object msg = write.Msg; IPromise promise = write.Promise; Recycle(write, false); if (!promise.IsVoid) { combiner.Add(promise.Task); } _ = _ctx.WriteAsync(msg, promise); write = next; } } combiner.Finish(p); } catch (Exception exc) { p.SetException(exc); } AssertEmpty(); return(p.Task); }
private static void HandleEndOfInputFuture(Task task, PendingWrite currentWrite) { if (task.IsSuccess()) { var chunks = (IChunkedInput <T>)currentWrite.Message; // read state of the input in local variables before closing it long inputProgress = chunks.Progress; long inputLength = chunks.Length; CloseInput(chunks); currentWrite.Progress(inputProgress, inputLength); currentWrite.Success(inputLength); } else { CloseInput((IChunkedInput <T>)currentWrite.Message); currentWrite.Fail(task.Exception); } }
/// <summary> /// Remove a pending write operation and fail it with the given <see cref="Exception"/>. The message will be /// released via <see cref="ReferenceCountUtil.SafeRelease(object)"/>. /// </summary> /// <param name="cause">The <see cref="Exception"/> to fail with.</param> public void RemoveAndFail(Exception cause) { Debug.Assert(_ctx.Executor.InEventLoop); if (cause is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.cause); } PendingWrite write = _head; if (write is null) { return; } ReferenceCountUtil.SafeRelease(write.Msg); IPromise promise = write.Promise; Util.SafeSetFailure(promise, cause, Logger); Recycle(write, true); }
/** * Remove all pending write operation and fail them with the given {@link Throwable}. The message will be released * via {@link ReferenceCountUtil#safeRelease(Object)}. */ public void RemoveAndFailAll(Exception cause) { Contract.Assert(this.ctx.Executor.InEventLoop); Contract.Requires(cause != null); // Guard against re-entrance by directly reset PendingWrite write = this.head; this.head = this.tail = null; this.size = 0; while (write != null) { PendingWrite next = write.Next; ReferenceCountUtil.SafeRelease(write.Msg); TaskCompletionSource promise = write.Promise; this.Recycle(write, false); Util.SafeSetFailure(promise, cause, Logger); write = next; } this.AssertEmpty(); }
/// <summary> /// Removes all pending write operation and performs them via <see cref="IChannelHandlerContext.WriteAsync(object, IPromise)"/> /// </summary> /// <returns>An await-able task.</returns> public Task RemoveAndWriteAllAsync() { Debug.Assert(_ctx.Executor.InEventLoop); if (IsEmpty) { return(TaskUtil.Completed); } // Guard against re-entrance by directly reset int currentSize = _size; var tasks = new List <Task>(currentSize); // It is possible for some of the written promises to trigger more writes. The new writes // will "revive" the queue, so we need to write them up until the queue is empty. for (PendingWrite write = _head; write is object; write = _head) { _head = _tail = null; _size = 0; _bytes = 0; while (write is object) { PendingWrite next = write.Next; object msg = write.Msg; IPromise promise = write.Promise; Recycle(write, false); if (!promise.IsVoid) { tasks.Add(promise.Task); } _ = _ctx.WriteAsync(msg, promise); write = next; } } AssertEmpty(); return(Task.WhenAll(tasks)); }
/** * Remove all pending write operation and performs them via * {@link ChannelHandlerContext#write(Object, ChannelPromise)}. * * @return {@link ChannelFuture} if something was written and {@code null} * if the {@link PendingWriteQueue} is empty. */ public Task RemoveAndWriteAllAsync() { Contract.Assert(this.ctx.Executor.InEventLoop); if (this.size == 1) { // No need to use ChannelPromiseAggregator for this case. return(this.RemoveAndWriteAsync()); } PendingWrite write = this.head; if (write == null) { // empty so just return null return(null); } // Guard against re-entrance by directly reset this.head = this.tail = null; int currentSize = this.size; this.size = 0; var tasks = new List <Task>(currentSize); while (write != null) { PendingWrite next = write.Next; object msg = write.Msg; TaskCompletionSource promise = write.Promise; this.Recycle(write, false); this.ctx.WriteAsync(msg).LinkOutcome(promise); tasks.Add(promise.Task); write = next; } this.AssertEmpty(); return(Task.WhenAll(tasks)); }
/** * Add the given {@code msg} and {@link ChannelPromise}. */ public Task Add(object msg) { Contract.Assert(this.ctx.Executor.InEventLoop); Contract.Requires(msg != null); int messageSize = this.estimatorHandle.Size(msg); if (messageSize < 0) { // Size may be unknow so just use 0 messageSize = 0; } var promise = new TaskCompletionSource(); PendingWrite write = PendingWrite.NewInstance(msg, messageSize, promise); PendingWrite currentTail = this.tail; if (currentTail == null) { this.tail = this.head = write; } else { currentTail.Next = write; this.tail = write; } this.size++; // We need to guard against null as channel.Unsafe.OutboundBuffer may returned null // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 if (this.buffer != null) { this.buffer.IncrementPendingOutboundBytes(write.Size); } return promise.Task; }
private async Task AppendInternalAsync(PendingWrite mine) { using (await _asyncLock.LockAsync()) { var completed = new List<PendingWrite>(); try { if (mine.Done()) return; await MakeRoomForWrite(mine.Stream.Length, mine.Etag); var seq = _options.Status.NextSeqeunce(); var room = _currentBufferSize - _memTable.MemTable.ApproximateMemoryUsage; PendingWrite write; while (_pending.TryDequeue(out write)) { var memoryHandle = _memTable.MemTable.Write(write.Stream); await _memTable.Log.Write7BitEncodedIntAsync(memoryHandle.Size); using (var stream = _memTable.MemTable.Read(memoryHandle)) { await _memTable.Log.CopyFromAsync(stream); } _memTable.MemTable.Add(seq, ItemType.Value, EtagToSlice(write.Etag), memoryHandle); room -= memoryHandle.Size; completed.Add(write); PendingWrite next; if (_pending.TryPeek(out next) == false) break; if (next.Stream.Length > room) // don't use more than the current mem table allows break; } foreach (var pendingWrite in completed) { pendingWrite.Result.SetResult(null); } } catch (Exception e) { foreach (var pendingWrite in completed) { pendingWrite.Result.SetException(e); } throw; } finally { _writeCompletedEvent.Pulse(); } } }
void DoFlush(IChannelHandlerContext context) { IChannel channel = context.Channel; if (!channel.Active) { this.Discard(); return; } bool requiresFlush = true; IByteBufferAllocator allocator = context.Allocator; while (channel.IsWritable) { if (this.currentWrite == null) { this.currentWrite = this.queue.Count > 0 ? this.queue.Dequeue() : null; } if (this.currentWrite == null) { break; } PendingWrite current = this.currentWrite; object pendingMessage = current.Message; var chunks = pendingMessage as IChunkedInput <T>; if (chunks != null) { bool endOfInput; bool suspend; object message = null; try { message = chunks.ReadChunk(allocator); endOfInput = chunks.IsEndOfInput; if (message == null) { // No need to suspend when reached at the end. suspend = !endOfInput; } else { suspend = false; } } catch (Exception exception) { this.currentWrite = null; if (message != null) { ReferenceCountUtil.Release(message); } current.Fail(exception); CloseInput(chunks); break; } if (suspend) { // ChunkedInput.nextChunk() returned null and it has // not reached at the end of input. Let's wait until // more chunks arrive. Nothing to write or notify. break; } if (message == null) { // If message is null write an empty ByteBuf. // See https://github.com/netty/netty/issues/1671 message = Unpooled.Empty; } Task future = context.WriteAsync(message); if (endOfInput) { this.currentWrite = null; // Register a listener which will close the input once the write is complete. // This is needed because the Chunk may have some resource bound that can not // be closed before its not written. // // See https://github.com/netty/netty/issues/303 future.ContinueWith((_, state) => { var pendingTask = (PendingWrite)state; CloseInput((IChunkedInput <T>)pendingTask.Message); pendingTask.Success(); }, current, TaskContinuationOptions.ExecuteSynchronously); } else if (channel.IsWritable) { future.ContinueWith((task, state) => { var pendingTask = (PendingWrite)state; if (task.IsFaulted) { CloseInput((IChunkedInput <T>)pendingTask.Message); pendingTask.Fail(task.Exception); } else { pendingTask.Progress(chunks.Progress, chunks.Length); } }, current, TaskContinuationOptions.ExecuteSynchronously); } else { future.ContinueWith((task, state) => { var handler = (ChunkedWriteHandler <T>)state; if (task.IsFaulted) { CloseInput((IChunkedInput <T>)handler.currentWrite.Message); handler.currentWrite.Fail(task.Exception); } else { handler.currentWrite.Progress(chunks.Progress, chunks.Length); if (channel.IsWritable) { handler.ResumeTransfer(); } } }, this, TaskContinuationOptions.ExecuteSynchronously); } // Flush each chunk to conserve memory context.Flush(); requiresFlush = false; } else { context.WriteAsync(pendingMessage) .ContinueWith((task, state) => { var pendingTask = (PendingWrite)state; if (task.IsFaulted) { pendingTask.Fail(task.Exception); } else { pendingTask.Success(); } }, current, TaskContinuationOptions.ExecuteSynchronously); this.currentWrite = null; requiresFlush = true; } if (!channel.Active) { this.Discard(new ClosedChannelException()); break; } } if (requiresFlush) { context.Flush(); } }
private static void HandleFuture(Task task, ChunkedWriteHandler <T> owner, IChannel channel, PendingWrite currentWrite, bool resume) { if (task.IsSuccess()) { var chunks = (IChunkedInput <T>)currentWrite.Message; currentWrite.Progress(chunks.Progress, chunks.Length); if (resume && channel.IsWritable) { owner.ResumeTransfer(); } } else { CloseInput((IChunkedInput <T>)currentWrite.Message); currentWrite.Fail(task.Exception); } }
private Receive HandleWriteMessages(ConnectionInfo info) { return(message => { switch (message) { case SocketSent _: AcknowledgeSent(); if (IsWritePending) { DoWrite(info); if (!IsWritePending && interestedInResume != null) { interestedInResume.Tell(WritingResumed.Instance); interestedInResume = null; } } return true; case WriteCommand write: if (HasStatus(ConnectionStatus.WritingSuspended)) { if (traceLogging) { Log.Debug("Dropping write because writing is suspended"); } Sender.Tell(write.FailureMessage); } else if (IsWritePending) { if (traceLogging) { Log.Debug("Dropping write because queue is full"); } Sender.Tell(write.FailureMessage); if (info.UseResumeWriting) { SetStatus(ConnectionStatus.WritingSuspended); } } else { pendingWrite = CreatePendingWrite(Sender, write, info); if (IsWritePending) { DoWrite(info); } } return true; case ResumeWriting _: /* * If more than one actor sends Writes then the first to send this * message might resume too early for the second, leading to a Write of * the second to go through although it has not been resumed yet; there * is nothing we can do about this apart from all actors needing to * register themselves and us keeping track of them, which sounds bad. * * Thus it is documented that useResumeWriting is incompatible with * multiple writers. But we fail as gracefully as we can. */ ClearStatus(ConnectionStatus.WritingSuspended); if (IsWritePending) { if (interestedInResume == null) { interestedInResume = Sender; } else { Sender.Tell(new CommandFailed(ResumeWriting.Instance)); } } else { Sender.Tell(WritingResumed.Instance); } return true; case UpdatePendingWriteAndThen updatePendingWrite: pendingWrite = updatePendingWrite.RemainingWrite; updatePendingWrite.Work(); if (IsWritePending) { DoWrite(info); } return true; case WriteFileFailed fail: HandleError(info.Handler, fail.Cause); return true; default: return false; } }); }
private Receive HandleWriteMessages(ConnectionInfo info) { return(message => { if (message is SelectionHandler.ChannelWritable) { if (WritePending()) { DoWrite(info); if (!WritePending() && _interestedInResume != null) { _interestedInResume.Tell(IO.Tcp.WritingResumed.Instance); _interestedInResume = null; } } return true; } var write = message as Tcp.WriteCommand; if (write != null) { if (_writingSuspended) { if (_tcp.Settings.TraceLogging) { _log.Debug("Dropping write because writing is suspended"); } Sender.Tell(write.FailureMessage); } else if (WritePending()) { if (_tcp.Settings.TraceLogging) { _log.Debug("Dropping write because queue is full"); } Sender.Tell(write.FailureMessage); if (info.UseResumeWriting) { _writingSuspended = true; } } else { _pendingWrite = CreatePendingWrite(Sender, write); if (WritePending()) { DoWrite(info); } } return true; } if (message is Tcp.ResumeWriting) { /* * If more than one actor sends Writes then the first to send this * message might resume too early for the second, leading to a Write of * the second to go through although it has not been resumed yet; there * is nothing we can do about this apart from all actors needing to * register themselves and us keeping track of them, which sounds bad. * * Thus it is documented that useResumeWriting is incompatible with * multiple writers. But we fail as gracefully as we can. */ _writingSuspended = false; if (WritePending()) { if (_interestedInResume == null) { _interestedInResume = Sender; } else { Sender.Tell(new Tcp.CommandFailed(IO.Tcp.ResumeWriting.Instance)); } } else { Sender.Tell(IO.Tcp.WritingResumed.Instance); } return true; } var updatePendingWrite = message as UpdatePendingWriteAndThen; if (updatePendingWrite != null) { _pendingWrite = updatePendingWrite.RemainingWrite; updatePendingWrite.Work(); if (WritePending()) { info.Registration.EnableInterest(SocketAsyncOperation.Send); } return true; } //TODO: File IO return false; }); }
public UpdatePendingWriteAndThen(PendingWrite remainingWrite, Action work) { RemainingWrite = remainingWrite; Work = work; }
private void DoWrite(ConnectionInfo info) { _pendingWrite = _pendingWrite.DoWrite(info); }
public async Task AppendAsync(RavenJObject data) { if (disposed) throw new ObjectDisposedException("EventStream"); var nextEtag = _options.Status.NextEtag(); data["@etag"] = nextEtag.ToString(); data["@date"] = DateTime.UtcNow.ToString("o"); using (var stream = new BufferPoolMemoryStream(_options.BufferPool)) { var bsonWriter = new BsonWriter(stream); data.WriteTo(bsonWriter); bsonWriter.Flush(); stream.Position = 0; var mine = new PendingWrite(stream, nextEtag); _pending.Enqueue(mine); while (mine.Done() == false && _pending.Peek() != mine) { await _writeCompletedEvent.WaitAsync(); } if (mine.Done()) return; await AppendInternalAsync(mine); } }
/** * Remove all pending write operation and fail them with the given {@link Throwable}. The message will be released * via {@link ReferenceCountUtil#safeRelease(Object)}. */ public void RemoveAndFailAll(Exception cause) { Contract.Assert(this.ctx.Executor.InEventLoop); Contract.Requires(cause != null); // Guard against re-entrance by directly reset PendingWrite write = this.head; this.head = this.tail = null; this.size = 0; while (write != null) { PendingWrite next = write.Next; ReferenceCountUtil.SafeRelease(write.Msg); TaskCompletionSource promise = write.Promise; this.Recycle(write, false); SafeFail(promise, cause); write = next; } this.AssertEmpty(); }
void DoFlush(IChannelHandlerContext context) { IChannel channel = context.Channel; if (!channel.Active) { Discard(); return; } bool requiresFlush = true; IByteBufferAllocator allocator = context.Allocator; while (channel.IsWritable) { PendingWrite currentWrite = _queue.FirstOrDefault; if (currentWrite is null) { break; } if (currentWrite.Promise.IsCompleted) { // This might happen e.g. in the case when a write operation // failed, but there're still unconsumed chunks left. // Most chunked input sources would stop generating chunks // and report end of input, but this doesn't work with any // source wrapped in HttpChunkedInput. // Note, that we're not trying to release the message/chunks // as this had to be done already by someone who resolved the // promise (using ChunkedInput.close method). // See https://github.com/netty/netty/issues/8700. _ = _queue.RemoveFromFront(); continue; } object pendingMessage = currentWrite.Message; if (pendingMessage is IChunkedInput <T> chunks) { bool endOfInput; bool suspend; object message = null; try { message = chunks.ReadChunk(allocator); endOfInput = chunks.IsEndOfInput; if (message is null) { // No need to suspend when reached at the end. suspend = !endOfInput; } else { suspend = false; } } catch (Exception exception) { _ = _queue.RemoveFromFront(); if (message is object) { _ = ReferenceCountUtil.Release(message); } CloseInput(chunks); currentWrite.Fail(exception); break; } if (suspend) { // ChunkedInput.nextChunk() returned null and it has // not reached at the end of input. Let's wait until // more chunks arrive. Nothing to write or notify. break; } if (message is null) { // If message is null write an empty ByteBuf. // See https://github.com/netty/netty/issues/1671 message = Unpooled.Empty; } // Flush each chunk to conserve memory Task future = context.WriteAndFlushAsync(message); if (endOfInput) { _ = _queue.RemoveFromFront(); if (future.IsCompleted) { HandleEndOfInputFuture(future, currentWrite); } else { // Register a listener which will close the input once the write is complete. // This is needed because the Chunk may have some resource bound that can not // be closed before its not written. // // See https://github.com/netty/netty/issues/303 _ = future.ContinueWith(LinkOutcomeWhenIsEndOfChunkedInputAction, currentWrite, TaskContinuationOptions.ExecuteSynchronously); } } else { var resume = !channel.IsWritable; if (future.IsCompleted) { HandleFuture(future, this, channel, currentWrite, resume); } else { _ = future.ContinueWith(LinkOutcomeAction, Tuple.Create(this, channel, currentWrite, resume), TaskContinuationOptions.ExecuteSynchronously); } } requiresFlush = false; } else { _ = _queue.RemoveFromFront(); _ = context.WriteAsync(pendingMessage, currentWrite.Promise); requiresFlush = true; } if (!channel.Active) { Discard(new ClosedChannelException()); break; } } if (requiresFlush) { _ = context.Flush(); } }
void Recycle(PendingWrite write, bool update) { PendingWrite next = write.Next; long writeSize = write.Size; if (update) { if (next == null) { // Handled last PendingWrite so rest head and tail // Guard against re-entrance by directly reset this.head = this.tail = null; this.size = 0; } else { this.head = next; this.size--; Contract.Assert(this.size > 0); } } write.Recycle(); // We need to guard against null as channel.unsafe().outboundBuffer() may returned null // if the channel was already closed when constructing the PendingWriteQueue. // See https://github.com/netty/netty/issues/3967 if (this.buffer != null) { this.buffer.DecrementPendingOutboundBytes(writeSize); } }
/** * Remove all pending write operation and performs them via * {@link ChannelHandlerContext#write(Object, ChannelPromise)}. * * @return {@link ChannelFuture} if something was written and {@code null} * if the {@link PendingWriteQueue} is empty. */ public Task RemoveAndWriteAll() { Contract.Assert(this.ctx.Executor.InEventLoop); if (this.size == 1) { // No need to use ChannelPromiseAggregator for this case. return this.RemoveAndWrite(); } PendingWrite write = this.head; if (write == null) { // empty so just return null return null; } // Guard against re-entrance by directly reset this.head = this.tail = null; int currentSize = this.size; this.size = 0; var tasks = new List<Task>(currentSize); while (write != null) { PendingWrite next = write.Next; object msg = write.Msg; TaskCompletionSource promise = write.Promise; this.Recycle(write, false); this.ctx.WriteAsync(msg).LinkOutcome(promise); tasks.Add(promise.Task); write = next; } this.AssertEmpty(); return Task.WhenAll(tasks); }