internal void Write(int numBytes, IStreamByteDistributorWriter writer) { Debug.Assert(_stream is object); try { writer.Write(_stream, numBytes); } catch (Exception t) { ThrowHelper.ThrowConnectionError_ByteDistributionWriteError(t); } }
/// <summary> /// Write any allocated bytes for the given stream and updates the streamable bytes, /// assuming all of the bytes will be written. /// </summary> /// <param name="numBytes"></param> /// <param name="writer"></param> public void Write(int numBytes, IStreamByteDistributorWriter writer) { _writing = true; try { // Write the allocated bytes. writer.Write(_stream, numBytes); } catch (Exception t) { ThrowHelper.ThrowConnectionError_ByteDistributionWriteError(t); } finally { _writing = false; } }
int Distribute(int maxBytes, IStreamByteDistributorWriter writer, State state) { if (state.IsActive()) { int nsent = Math.Min(maxBytes, state._streamableBytes); state.Write(nsent, writer); if (0u >= (uint)nsent && maxBytes != 0) { // If a stream sends zero bytes, then we gave it a chance to write empty frames and it is now // considered inactive until the next call to updateStreamableBytes. This allows descendant streams to // be allocated bytes when the parent stream can't utilize them. This may be as a result of the // stream's flow control window being 0. state.UpdateStreamableBytes(state._streamableBytes, false); } return(nsent); } return(DistributeToChildren(maxBytes, writer, state)); }
/// <summary> /// It is a pre-condition that <see cref="State.PollPseudoTimeQueue"/> returns a non-<c>null</c> value. This is a result of the way /// the allocation algorithm is structured and can be explained in the following cases: /// <h3>For the recursive case</h3> /// If a stream has no children (in the allocation tree) than that node must be active or it will not be in the /// allocation tree. If a node is active then it will not delegate to children and recursion ends. /// <h3>For the initial case</h3> /// We check connectionState.activeCountForTree == 0 before any allocation is done. So if the connection stream /// has no active children we don't get into this method. /// </summary> /// <param name="maxBytes"></param> /// <param name="writer"></param> /// <param name="state"></param> /// <returns></returns> int DistributeToChildren(int maxBytes, IStreamByteDistributorWriter writer, State state) { long oldTotalQueuedWeights = state._totalQueuedWeights; State childState = state.PollPseudoTimeQueue(); State nextChildState = state.PeekPseudoTimeQueue(); childState.SetDistributing(); try { Debug.Assert( nextChildState is null || nextChildState._pseudoTimeToWrite >= childState._pseudoTimeToWrite, $"nextChildState[{nextChildState?._streamId}].pseudoTime({nextChildState?._pseudoTimeToWrite}) < childState[{childState._streamId}].pseudoTime({childState._pseudoTimeToWrite})"); int nsent = Distribute( nextChildState is null ? maxBytes : Math.Min( maxBytes, (int)Math.Min( (nextChildState._pseudoTimeToWrite - childState._pseudoTimeToWrite) * childState._weight / oldTotalQueuedWeights + _allocationQuantum, int.MaxValue ) ), writer, childState); state._pseudoTime += nsent; childState.UpdatePseudoTime(state, nsent, oldTotalQueuedWeights); return(nsent); } finally { childState.UnsetDistributing(); // Do in finally to ensure the internal flags is not corrupted if an exception is thrown. // The offer operation is delayed until we unroll up the recursive stack, so we don't have to remove from // the priority pseudoTimeQueue due to a write operation. if (childState._activeCountForTree != 0) { state.OfferPseudoTimeQueue(childState); } } }
public bool Distribute(int maxBytes, IStreamByteDistributorWriter writer) { var size = _queue.Count; if (0u >= (uint)size) { return(_totalStreamableBytes > 0L); } var chunkSize = Math.Max(_minAllocationChunk, maxBytes / size); State state = _queue.RemoveFirst(); do { state._enqueued = false; if (state._windowNegative) { continue; } if (0u >= (uint)maxBytes && state._streamableBytes > 0) { // Stop at the first state that can't send. Add this state back to the head of the queue. Note // that empty frames at the head of the queue will always be written, assuming the stream window // is not negative. _queue.AddFirst(state); state._enqueued = true; break; } // Allocate as much data as we can for this stream. int chunk = Math.Min(chunkSize, Math.Min(maxBytes, state._streamableBytes)); maxBytes -= chunk; // Write the allocated bytes and enqueue as necessary. state.Write(chunk, writer); } while (_queue.TryRemoveFirst(out state)); return(_totalStreamableBytes > 0L); }
public bool Distribute(int maxBytes, IStreamByteDistributorWriter writer) { // As long as there is some active frame we should write at least 1 time. if (0u >= (uint)_connectionState._activeCountForTree) { return(false); } // The goal is to write until we write all the allocated bytes or are no longer making progress. // We still attempt to write even after the number of allocated bytes has been exhausted to allow empty frames // to be sent. Making progress means the active streams rooted at the connection stream has changed. int oldIsActiveCountForTree; do { oldIsActiveCountForTree = _connectionState._activeCountForTree; // connectionState will never be active, so go right to its children. maxBytes -= DistributeToChildren(maxBytes, writer, _connectionState); }while (_connectionState._activeCountForTree != 0 && (maxBytes > 0 || oldIsActiveCountForTree != _connectionState._activeCountForTree)); return(_connectionState._activeCountForTree != 0); }