diff --git a/src/DotNetty.Buffers/AbstractByteBuffer.cs b/src/DotNetty.Buffers/AbstractByteBuffer.cs index 96e85bde0..8c9e42f1d 100644 --- a/src/DotNetty.Buffers/AbstractByteBuffer.cs +++ b/src/DotNetty.Buffers/AbstractByteBuffer.cs @@ -560,16 +560,20 @@ public virtual IByteBuffer SetShortLE(int index, int value) protected internal abstract void _SetShortLE(int index, int value); - public virtual IByteBuffer SetUnsignedShort(int index, ushort value) + public IByteBuffer SetUnsignedShort(int index, ushort value) { - this.SetShort(index, value); - return this; + unchecked + { + return this.SetShort(index, (short)value); + } } - public virtual IByteBuffer SetUnsignedShortLE(int index, ushort value) + public IByteBuffer SetUnsignedShortLE(int index, ushort value) { - this.SetShortLE(index, value); - return this; + unchecked + { + return this.SetShortLE(index, (short)value); + } } public virtual IByteBuffer SetChar(int index, char value) diff --git a/src/DotNetty.Buffers/AdvancedLeakAwareByteBuffer.cs b/src/DotNetty.Buffers/AdvancedLeakAwareByteBuffer.cs index 4bae93d78..cbf9d5c56 100644 --- a/src/DotNetty.Buffers/AdvancedLeakAwareByteBuffer.cs +++ b/src/DotNetty.Buffers/AdvancedLeakAwareByteBuffer.cs @@ -771,6 +771,30 @@ public override IReferenceCounted Retain(int increment) return base.Retain(increment); } + public override IByteBuffer RetainedSlice() + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.RetainedSlice(); + } + + public override IByteBuffer RetainedSlice(int index, int length) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.RetainedSlice(index, length); + } + + public override IByteBuffer RetainedDuplicate() + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.RetainedDuplicate(); + } + + public override IByteBuffer ReadRetainedSlice(int length) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.ReadRetainedSlice(length); + } + public override IReferenceCounted Touch() { this.Leak.Record(); diff --git a/src/DotNetty.Buffers/AdvancedLeakAwareCompositeByteBuffer.cs b/src/DotNetty.Buffers/AdvancedLeakAwareCompositeByteBuffer.cs index ec8354877..cbd582ce1 100644 --- a/src/DotNetty.Buffers/AdvancedLeakAwareCompositeByteBuffer.cs +++ b/src/DotNetty.Buffers/AdvancedLeakAwareCompositeByteBuffer.cs @@ -4,6 +4,7 @@ namespace DotNetty.Buffers { using System; + using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; @@ -593,6 +594,156 @@ public override IByteBuffer GetBytes(int index, Stream destination, int length) return base.GetBytes(index, destination, length); } + public override CompositeByteBuffer AddComponent(bool increaseWriterIndex, IByteBuffer buffer) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.AddComponent(increaseWriterIndex, buffer); + } + + public override CompositeByteBuffer AddComponent(bool increaseWriterIndex, int cIndex, IByteBuffer buffer) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.AddComponent(increaseWriterIndex, cIndex, buffer); + } + + public override CompositeByteBuffer AddComponent(IByteBuffer buffer) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.AddComponent(buffer); + } + + public override CompositeByteBuffer AddComponent(int cIndex, IByteBuffer buffer) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.AddComponent(cIndex, buffer); + } + + public override CompositeByteBuffer AddComponents(bool increaseWriterIndex, params IByteBuffer[] buffers) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.AddComponents(increaseWriterIndex, buffers); + } + + public override CompositeByteBuffer AddComponents(bool increaseWriterIndex, IEnumerable buffers) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.AddComponents(increaseWriterIndex, buffers); + } + + public override CompositeByteBuffer AddComponents(IEnumerable buffers) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.AddComponents(buffers); + } + + public override CompositeByteBuffer AddComponents(int cIndex, IEnumerable buffers) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.AddComponents(cIndex, buffers); + } + + public override CompositeByteBuffer AddComponents(int cIndex, params IByteBuffer[] buffers) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.AddComponents(cIndex, buffers); + } + + public override CompositeByteBuffer AddComponents(params IByteBuffer[] buffers) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.AddComponents(buffers); + } + + public override CompositeByteBuffer RemoveComponent(int cIndex) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.RemoveComponent(cIndex); + } + + public override CompositeByteBuffer RemoveComponents(int cIndex, int numComponents) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.RemoveComponents(cIndex, numComponents); + } + + public override CompositeByteBuffer Consolidate() + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.Consolidate(); + } + + public override CompositeByteBuffer Consolidate(int cIndex, int numComponents) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.Consolidate(cIndex, numComponents); + } + + public override CompositeByteBuffer DiscardReadComponents() + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.DiscardReadComponents(); + } + + public override IList Decompose(int offset, int length) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.Decompose(offset, length); + } + + public override ICharSequence GetCharSequence(int index, int length, Encoding encoding) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.GetCharSequence(index, length, encoding); + } + + public override ICharSequence ReadCharSequence(int length, Encoding encoding) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.ReadCharSequence(length, encoding); + } + + public override int SetCharSequence(int index, ICharSequence sequence, Encoding encoding) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.SetCharSequence(index, sequence, encoding); + } + + public override int WriteCharSequence(ICharSequence sequence, Encoding encoding) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.WriteCharSequence(sequence, encoding); + } + + public override string GetString(int index, int length, Encoding encoding) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.GetString(index, length, encoding); + } + + public override string ReadString(int length, Encoding encoding) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.ReadString(length, encoding); + } + + public override int SetString(int index, string value, Encoding encoding) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.SetString(index, value, encoding); + } + + public override int WriteString(string value, Encoding encoding) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.WriteString(value, encoding); + } + + public override IEnumerator GetEnumerator() + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.GetEnumerator(); + } + public override IReferenceCounted Retain() { this.Leak.Record(); @@ -605,6 +756,30 @@ public override IReferenceCounted Retain(int increment) return base.Retain(increment); } + public override IByteBuffer RetainedSlice() + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.RetainedSlice(); + } + + public override IByteBuffer RetainedSlice(int index, int length) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.RetainedSlice(index, length); + } + + public override IByteBuffer RetainedDuplicate() + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.RetainedDuplicate(); + } + + public override IByteBuffer ReadRetainedSlice(int length) + { + RecordLeakNonRefCountingOperation(this.Leak); + return base.ReadRetainedSlice(length); + } + public override bool Release() { this.Leak.Record(); diff --git a/src/DotNetty.Buffers/SimpleLeakAwareCompositeByteBuffer.cs b/src/DotNetty.Buffers/SimpleLeakAwareCompositeByteBuffer.cs index 97f3c95f2..21f3c3a9e 100644 --- a/src/DotNetty.Buffers/SimpleLeakAwareCompositeByteBuffer.cs +++ b/src/DotNetty.Buffers/SimpleLeakAwareCompositeByteBuffer.cs @@ -61,6 +61,14 @@ void CloseLeak(IByteBuffer trackedByteBuf) public override IByteBuffer ReadSlice(int length) => this.NewLeakAwareByteBuffer(base.ReadSlice(length)); + public override IByteBuffer RetainedSlice() => this.NewLeakAwareByteBuffer(base.RetainedSlice()); + + public override IByteBuffer RetainedSlice(int index, int length) => this.NewLeakAwareByteBuffer(base.RetainedSlice(index, length)); + + public override IByteBuffer RetainedDuplicate() => this.NewLeakAwareByteBuffer(base.RetainedDuplicate()); + + public override IByteBuffer ReadRetainedSlice(int length) => this.NewLeakAwareByteBuffer(base.ReadRetainedSlice(length)); + SimpleLeakAwareByteBuffer NewLeakAwareByteBuffer(IByteBuffer wrapped) => this.NewLeakAwareByteBuffer(wrapped, this.Unwrap(), this.Leak); protected virtual SimpleLeakAwareByteBuffer NewLeakAwareByteBuffer(IByteBuffer wrapped, IByteBuffer trackedByteBuf, IResourceLeakTracker leakTracker) => diff --git a/src/DotNetty.Buffers/UnpooledUnsafeDirectByteBuffer.cs b/src/DotNetty.Buffers/UnpooledUnsafeDirectByteBuffer.cs index e0f7e8c8e..ff3d52781 100644 --- a/src/DotNetty.Buffers/UnpooledUnsafeDirectByteBuffer.cs +++ b/src/DotNetty.Buffers/UnpooledUnsafeDirectByteBuffer.cs @@ -297,7 +297,9 @@ public override Task SetBytesAsync(int index, Stream src, int length, Cance { this.CheckIndex(index, length); fixed (byte* addr = &this.Addr(index)) + { return UnsafeByteBufferUtil.SetBytesAsync(this, addr, index, src, length, cancellationToken); + } } public override int IoBufferCount => 1; diff --git a/src/DotNetty.Buffers/WrappedCompositeByteBuffer.cs b/src/DotNetty.Buffers/WrappedCompositeByteBuffer.cs index 07dff4b7c..3fb8453ee 100644 --- a/src/DotNetty.Buffers/WrappedCompositeByteBuffer.cs +++ b/src/DotNetty.Buffers/WrappedCompositeByteBuffer.cs @@ -110,13 +110,13 @@ internal WrappedCompositeByteBuffer(CompositeByteBuffer wrapped) : base(wrapped. public override int ForEachByteDesc(int index, int length, IByteProcessor processor) => this.wrapped.ForEachByteDesc(index, length, processor); - public override int GetHashCode() => this.wrapped.GetHashCode(); + public sealed override int GetHashCode() => this.wrapped.GetHashCode(); - public override bool Equals(IByteBuffer buf) => this.wrapped.Equals(buf); + public sealed override bool Equals(IByteBuffer buf) => this.wrapped.Equals(buf); - public override int CompareTo(IByteBuffer that) => this.wrapped.CompareTo(that); + public sealed override int CompareTo(IByteBuffer that) => this.wrapped.CompareTo(that); - public override int ReferenceCount => this.wrapped.ReferenceCount; + public sealed override int ReferenceCount => this.wrapped.ReferenceCount; public override IByteBuffer Duplicate() => this.wrapped.Duplicate(); @@ -669,5 +669,25 @@ public override IByteBuffer DiscardSomeReadBytes() protected internal sealed override void Deallocate() => this.wrapped.Deallocate(); public sealed override IByteBuffer Unwrap() => this.wrapped; + + public sealed override IntPtr AddressOfPinnedMemory() => this.wrapped.AddressOfPinnedMemory(); + + public sealed override ref byte GetPinnableMemoryAddress() => ref this.wrapped.GetPinnableMemoryAddress(); + + public sealed override bool HasMemoryAddress => this.wrapped.HasMemoryAddress; + + public sealed override bool IsWritable(int size) => this.wrapped.IsWritable(size); + + public sealed override int MaxCapacity => this.wrapped.MaxCapacity; + + public sealed override bool IsDirect => this.wrapped.IsDirect; + + public override IByteBuffer ReadRetainedSlice(int length) => this.wrapped.ReadRetainedSlice(length); + + public override IByteBuffer RetainedDuplicate() => this.wrapped.RetainedDuplicate(); + + public override IByteBuffer RetainedSlice() => this.wrapped.RetainedSlice(); + + public override IByteBuffer RetainedSlice(int index, int length) => this.wrapped.RetainedSlice(index, length); } } \ No newline at end of file diff --git a/src/DotNetty.Common/Deque.cs b/src/DotNetty.Common/Deque.cs deleted file mode 100644 index e99461c04..000000000 --- a/src/DotNetty.Common/Deque.cs +++ /dev/null @@ -1,893 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Nito -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Reflection; - - /// - /// A double-ended queue (deque), which provides O(1) indexed access, O(1) removals from the front and back, amortized - /// O(1) insertions to the front and back, and O(N) insertions and removals anywhere else (with the operations getting - /// slower as the index approaches the middle). - /// - /// The type of elements contained in the deque. - [DebuggerDisplay("Count = {Count}, Capacity = {Capacity}")] - [DebuggerTypeProxy(typeof(Deque<>.DebugView))] - sealed class Deque : IList, IList - { - /// - /// The default capacity. - /// - const int DefaultCapacity = 8; - - /// - /// The circular buffer that holds the view. - /// - T[] buffer; - - /// - /// The offset into where the view begins. - /// - int offset; - - /// - /// Initializes a new instance of the class with the specified capacity. - /// - /// The initial capacity. Must be greater than 0. - public Deque(int capacity) - { - if (capacity < 1) - throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than 0."); - this.buffer = new T[capacity]; - } - - /// - /// Initializes a new instance of the class with the elements from the specified - /// collection. - /// - /// The collection. - public Deque(IEnumerable collection) - { - int count = collection.Count(); - if (count > 0) - { - this.buffer = new T[count]; - this.DoInsertRange(0, collection, count); - } - else - { - this.buffer = new T[DefaultCapacity]; - } - } - - /// - /// Initializes a new instance of the class. - /// - public Deque() - : this(DefaultCapacity) - { - } - - #region GenericListImplementations - - /// - /// Gets a value indicating whether this list is read-only. This implementation always returns false. - /// - /// true if this list is read-only; otherwise, false. - bool ICollection.IsReadOnly => false; - - /// - /// Gets or sets the item at the specified index. - /// - /// The index of the item to get or set. - /// is not a valid index in this list. - /// This property is set and the list is read-only. - public T this[int index] - { - get - { - CheckExistingIndexArgument(this.Count, index); - return this.DoGetItem(index); - } - - set - { - CheckExistingIndexArgument(this.Count, index); - this.DoSetItem(index, value); - } - } - - /// - /// Inserts an item to this list at the specified index. - /// - /// The zero-based index at which should be inserted. - /// The object to insert into this list. - /// - /// is not a valid index in this list. - /// - /// - /// This list is read-only. - /// - public void Insert(int index, T item) - { - CheckNewIndexArgument(this.Count, index); - this.DoInsert(index, item); - } - - /// - /// Removes the item at the specified index. - /// - /// The zero-based index of the item to remove. - /// - /// is not a valid index in this list. - /// - /// - /// This list is read-only. - /// - public void RemoveAt(int index) - { - CheckExistingIndexArgument(this.Count, index); - this.DoRemoveAt(index); - } - - /// - /// Determines the index of a specific item in this list. - /// - /// The object to locate in this list. - /// The index of if found in this list; otherwise, -1. - public int IndexOf(T item) - { - EqualityComparer comparer = EqualityComparer.Default; - int ret = 0; - foreach (T sourceItem in this) - { - if (comparer.Equals(item, sourceItem)) - return ret; - ++ret; - } - - return -1; - } - - /// - /// Adds an item to the end of this list. - /// - /// The object to add to this list. - /// - /// This list is read-only. - /// - void ICollection.Add(T item) - { - this.DoInsert(this.Count, item); - } - - /// - /// Determines whether this list contains a specific value. - /// - /// The object to locate in this list. - /// - /// true if is found in this list; otherwise, false. - /// - bool ICollection.Contains(T item) - { - return this.Contains(item, null); - } - - /// - /// Copies the elements of this list to an , starting at a particular - /// index. - /// - /// - /// The one-dimensional that is the destination of the elements copied - /// from this slice. The must have zero-based indexing. - /// - /// The zero-based index in at which copying begins. - /// - /// is null. - /// - /// - /// is less than 0. - /// - /// - /// is equal to or greater than the length of . - /// -or- - /// The number of elements in the source is greater than the - /// available space from to the end of the destination . - /// - void ICollection.CopyTo(T[] array, int arrayIndex) - { - if (array == null) - throw new ArgumentNullException(nameof(array), "Array is null"); - - int count = this.Count; - CheckRangeArguments(array.Length, arrayIndex, count); - for (int i = 0; i != count; ++i) - { - array[arrayIndex + i] = this[i]; - } - } - - /// - /// Removes the first occurrence of a specific object from this list. - /// - /// The object to remove from this list. - /// - /// true if was successfully removed from this list; otherwise, false. This method also - /// returns false if is not found in this list. - /// - /// - /// This list is read-only. - /// - public bool Remove(T item) - { - int index = this.IndexOf(item); - if (index == -1) - return false; - - this.DoRemoveAt(index); - return true; - } - - /// - /// Returns an enumerator that iterates through the collection. - /// - /// - /// A that can be used to iterate through the collection. - /// - public IEnumerator GetEnumerator() - { - int count = this.Count; - for (int i = 0; i != count; ++i) - { - yield return this.DoGetItem(i); - } - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - - #endregion - - #region ObjectListImplementations - - /// - /// Returns whether or not the type of a given item indicates it is appropriate for storing in this container. - /// - /// The item to test. - /// true if the item is appropriate to store in this container; otherwise, false. - bool ObjectIsT(object item) - { - if (item is T) - { - return true; - } - - if (item == null) - { - TypeInfo typeInfo = typeof(T).GetTypeInfo(); - if (typeInfo.IsClass && !typeInfo.IsPointer) - return true; // classes, arrays, and delegates - if (typeInfo.IsInterface) - return true; // interfaces - if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>)) - return true; // nullable value types - } - - return false; - } - - int IList.Add(object value) - { - if (!this.ObjectIsT(value)) - throw new ArgumentException("Item is not of the correct type.", nameof(value)); - this.AddToBack((T)value); - return this.Count - 1; - } - - bool IList.Contains(object value) - { - if (!this.ObjectIsT(value)) - throw new ArgumentException("Item is not of the correct type.", nameof(value)); - return this.Contains((T)value); - } - - int IList.IndexOf(object value) - { - if (!this.ObjectIsT(value)) - throw new ArgumentException("Item is not of the correct type.", nameof(value)); - return this.IndexOf((T)value); - } - - void IList.Insert(int index, object value) - { - if (!this.ObjectIsT(value)) - throw new ArgumentException("Item is not of the correct type.", nameof(value)); - this.Insert(index, (T)value); - } - - bool IList.IsFixedSize => false; - - bool IList.IsReadOnly => false; - - void IList.Remove(object value) - { - if (!this.ObjectIsT(value)) - throw new ArgumentException("Item is not of the correct type.", nameof(value)); - this.Remove((T)value); - } - - object IList.this[int index] - { - get { return this[index]; } - - set - { - if (!this.ObjectIsT(value)) - throw new ArgumentException("Item is not of the correct type.", nameof(value)); - this[index] = (T)value; - } - } - - void ICollection.CopyTo(Array array, int index) - { - if (array == null) - throw new ArgumentNullException(nameof(array), "Destination array cannot be null."); - CheckRangeArguments(array.Length, index, this.Count); - - for (int i = 0; i != this.Count; ++i) - { - try - { - array.SetValue(this[i], index + i); - } - catch (InvalidCastException ex) - { - throw new ArgumentException("Destination array is of incorrect type.", ex); - } - } - } - - bool ICollection.IsSynchronized => false; - - object ICollection.SyncRoot => this; - - #endregion - - #region GenericListHelpers - - /// - /// Checks the argument to see if it refers to a valid insertion point in a source of a given - /// length. - /// - /// The length of the source. This parameter is not checked for validity. - /// The index into the source. - /// - /// is not a valid index to an insertion point for - /// the source. - /// - static void CheckNewIndexArgument(int sourceLength, int index) - { - if (index < 0 || index > sourceLength) - { - throw new ArgumentOutOfRangeException(nameof(index), "Invalid new index " + index + " for source length " + sourceLength); - } - } - - /// - /// Checks the argument to see if it refers to an existing element in a source of a given - /// length. - /// - /// The length of the source. This parameter is not checked for validity. - /// The index into the source. - /// - /// is not a valid index to an existing element for - /// the source. - /// - static void CheckExistingIndexArgument(int sourceLength, int index) - { - if (index < 0 || index >= sourceLength) - { - throw new ArgumentOutOfRangeException(nameof(index), "Invalid existing index " + index + " for source length " + sourceLength); - } - } - - /// - /// Checks the and arguments for validity when applied to a source - /// of a given length. Allows 0-element ranges, including a 0-element range at the end of the source. - /// - /// The length of the source. This parameter is not checked for validity. - /// The index into source at which the range begins. - /// The number of elements in the range. - /// - /// Either or is less - /// than 0. - /// - /// The range [offset, offset + count) is not within the range [0, sourceLength). - static void CheckRangeArguments(int sourceLength, int offset, int count) - { - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), "Invalid offset " + offset); - } - - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), "Invalid count " + count); - } - - if (sourceLength - offset < count) - { - throw new ArgumentException("Invalid offset (" + offset + ") or count + (" + count + ") for source length " + sourceLength); - } - } - - #endregion - - /// - /// Gets a value indicating whether this instance is empty. - /// - bool IsEmpty => this.Count == 0; - - /// - /// Gets a value indicating whether this instance is at full capacity. - /// - bool IsFull => this.Count == this.Capacity; - - /// - /// Gets a value indicating whether the buffer is "split" (meaning the beginning of the view is at a later index in - /// than the end). - /// - bool IsSplit => this.offset > (this.Capacity - this.Count); - - /// - /// Gets or sets the capacity for this deque. This value must always be greater than zero, and this property cannot be - /// set to a value less than . - /// - /// Capacity cannot be set to a value less than . - public int Capacity - { - get { return this.buffer.Length; } - - set - { - if (value < 1) - throw new ArgumentOutOfRangeException(nameof(value), "Capacity must be greater than 0."); - - if (value < this.Count) - throw new InvalidOperationException("Capacity cannot be set to a value less than Count"); - - if (value == this.buffer.Length) - return; - - // Create the new buffer and copy our existing range. - var newBuffer = new T[value]; - if (this.IsSplit) - { - // The existing buffer is split, so we have to copy it in parts - int length = this.Capacity - this.offset; - Array.Copy(this.buffer, this.offset, newBuffer, 0, length); - Array.Copy(this.buffer, 0, newBuffer, length, this.Count - length); - } - else - { - // The existing buffer is whole - Array.Copy(this.buffer, this.offset, newBuffer, 0, this.Count); - } - - // Set up to use the new buffer. - this.buffer = newBuffer; - this.offset = 0; - } - } - - /// - /// Gets the number of elements contained in this deque. - /// - /// The number of elements contained in this deque. - public int Count { get; private set; } - - /// - /// Applies the offset to , resulting in a buffer index. - /// - /// The deque index. - /// The buffer index. - int DequeIndexToBufferIndex(int index) - { - return (index + this.offset) % this.Capacity; - } - - /// - /// Gets an element at the specified view index. - /// - /// The zero-based view index of the element to get. This index is guaranteed to be valid. - /// The element at the specified index. - T DoGetItem(int index) - { - return this.buffer[this.DequeIndexToBufferIndex(index)]; - } - - /// - /// Sets an element at the specified view index. - /// - /// The zero-based view index of the element to get. This index is guaranteed to be valid. - /// The element to store in the list. - void DoSetItem(int index, T item) - { - this.buffer[this.DequeIndexToBufferIndex(index)] = item; - } - - /// - /// Inserts an element at the specified view index. - /// - /// - /// The zero-based view index at which the element should be inserted. This index is guaranteed to be - /// valid. - /// - /// The element to store in the list. - void DoInsert(int index, T item) - { - this.EnsureCapacityForOneElement(); - - if (index == 0) - { - this.DoAddToFront(item); - return; - } - else if (index == this.Count) - { - this.DoAddToBack(item); - return; - } - - this.DoInsertRange(index, new[] { item }, 1); - } - - /// - /// Removes an element at the specified view index. - /// - /// The zero-based view index of the element to remove. This index is guaranteed to be valid. - void DoRemoveAt(int index) - { - if (index == 0) - { - this.DoRemoveFromFront(); - return; - } - else if (index == this.Count - 1) - { - this.DoRemoveFromBack(); - return; - } - - this.DoRemoveRange(index, 1); - } - - /// - /// Increments by using modulo- arithmetic. - /// - /// The value by which to increase . May not be negative. - /// The value of after it was incremented. - int PostIncrement(int value) - { - int ret = this.offset; - this.offset += value; - this.offset %= this.Capacity; - return ret; - } - - /// - /// Decrements by using modulo- arithmetic. - /// - /// - /// The value by which to reduce . May not be negative or greater than - /// . - /// - /// The value of before it was decremented. - int PreDecrement(int value) - { - this.offset -= value; - if (this.offset < 0) - this.offset += this.Capacity; - return this.offset; - } - - /// - /// Inserts a single element to the back of the view. must be false when this method is called. - /// - /// The element to insert. - void DoAddToBack(T value) - { - this.buffer[this.DequeIndexToBufferIndex(this.Count)] = value; - ++this.Count; - } - - /// - /// Inserts a single element to the front of the view. must be false when this method is called. - /// - /// The element to insert. - void DoAddToFront(T value) - { - this.buffer[this.PreDecrement(1)] = value; - ++this.Count; - } - - /// - /// Removes and returns the last element in the view. must be false when this method is called. - /// - /// The former last element. - T DoRemoveFromBack() - { - T ret = this.buffer[this.DequeIndexToBufferIndex(this.Count - 1)]; - --this.Count; - return ret; - } - - /// - /// Removes and returns the first element in the view. must be false when this method is called. - /// - /// The former first element. - T DoRemoveFromFront() - { - --this.Count; - return this.buffer[this.PostIncrement(1)]; - } - - /// - /// Inserts a range of elements into the view. - /// - /// The index into the view at which the elements are to be inserted. - /// The elements to insert. - /// - /// The number of elements in . Must be greater than zero, and - /// the sum of and must be less than or equal to - /// . - /// - void DoInsertRange(int index, IEnumerable collection, int collectionCount) - { - // Make room in the existing list - if (index < this.Count / 2) - { - // Inserting into the first half of the list - - // Move lower items down: [0, index) -> [Capacity - collectionCount, Capacity - collectionCount + index) - // This clears out the low "index" number of items, moving them "collectionCount" places down; - // after rotation, there will be a "collectionCount"-sized hole at "index". - int copyCount = index; - int writeIndex = this.Capacity - collectionCount; - for (int j = 0; j != copyCount; ++j) - this.buffer[this.DequeIndexToBufferIndex(writeIndex + j)] = this.buffer[this.DequeIndexToBufferIndex(j)]; - - // Rotate to the new view - this.PreDecrement(collectionCount); - } - else - { - // Inserting into the second half of the list - - // Move higher items up: [index, count) -> [index + collectionCount, collectionCount + count) - int copyCount = this.Count - index; - int writeIndex = index + collectionCount; - for (int j = copyCount - 1; j != -1; --j) - this.buffer[this.DequeIndexToBufferIndex(writeIndex + j)] = this.buffer[this.DequeIndexToBufferIndex(index + j)]; - } - - // Copy new items into place - int i = index; - foreach (T item in collection) - { - this.buffer[this.DequeIndexToBufferIndex(i)] = item; - ++i; - } - - // Adjust valid count - this.Count += collectionCount; - } - - /// - /// Removes a range of elements from the view. - /// - /// The index into the view at which the range begins. - /// - /// The number of elements in the range. This must be greater than 0 and less than or equal - /// to . - /// - void DoRemoveRange(int index, int collectionCount) - { - if (index == 0) - { - // Removing from the beginning: rotate to the new view - this.PostIncrement(collectionCount); - this.Count -= collectionCount; - return; - } - else if (index == this.Count - collectionCount) - { - // Removing from the ending: trim the existing view - this.Count -= collectionCount; - return; - } - - if ((index + (collectionCount / 2)) < this.Count / 2) - { - // Removing from first half of list - - // Move lower items up: [0, index) -> [collectionCount, collectionCount + index) - int copyCount = index; - int writeIndex = collectionCount; - for (int j = copyCount - 1; j != -1; --j) - this.buffer[this.DequeIndexToBufferIndex(writeIndex + j)] = this.buffer[this.DequeIndexToBufferIndex(j)]; - - // Rotate to new view - this.PostIncrement(collectionCount); - } - else - { - // Removing from second half of list - - // Move higher items down: [index + collectionCount, count) -> [index, count - collectionCount) - int copyCount = this.Count - collectionCount - index; - int readIndex = index + collectionCount; - for (int j = 0; j != copyCount; ++j) - this.buffer[this.DequeIndexToBufferIndex(index + j)] = this.buffer[this.DequeIndexToBufferIndex(readIndex + j)]; - } - - // Adjust valid count - this.Count -= collectionCount; - } - - /// - /// Doubles the capacity if necessary to make room for one more element. When this method returns, - /// is false. - /// - void EnsureCapacityForOneElement() - { - if (this.IsFull) - { - this.Capacity = this.Capacity * 2; - } - } - - /// - /// Inserts a single element at the back of this deque. - /// - /// The element to insert. - public void AddToBack(T value) - { - this.EnsureCapacityForOneElement(); - this.DoAddToBack(value); - } - - /// - /// Inserts a single element at the front of this deque. - /// - /// The element to insert. - public void AddToFront(T value) - { - this.EnsureCapacityForOneElement(); - this.DoAddToFront(value); - } - - /// - /// Inserts a collection of elements into this deque. - /// - /// The index at which the collection is inserted. - /// The collection of elements to insert. - /// - /// is not a valid index to an insertion point for - /// the source. - /// - public void InsertRange(int index, IEnumerable collection) - { - int collectionCount = collection.Count(); - CheckNewIndexArgument(this.Count, index); - - // Overflow-safe check for "this.Count + collectionCount > this.Capacity" - if (collectionCount > this.Capacity - this.Count) - { - this.Capacity = checked(this.Count + collectionCount); - } - - if (collectionCount == 0) - { - return; - } - - this.DoInsertRange(index, collection, collectionCount); - } - - /// - /// Removes a range of elements from this deque. - /// - /// The index into the deque at which the range begins. - /// The number of elements to remove. - /// - /// Either or is less - /// than 0. - /// - /// - /// The range [, + - /// ) is not within the range [0, ). - /// - public void RemoveRange(int offset, int count) - { - CheckRangeArguments(this.Count, offset, count); - - if (count == 0) - { - return; - } - - this.DoRemoveRange(offset, count); - } - - /// - /// Removes and returns the last element of this deque. - /// - /// The former last element. - /// The deque is empty. - public T RemoveFromBack() - { - if (this.IsEmpty) - throw new InvalidOperationException("The deque is empty."); - - return this.DoRemoveFromBack(); - } - - /// - /// Removes and returns the first element of this deque. - /// - /// The former first element. - /// The deque is empty. - public T RemoveFromFront() - { - if (this.IsEmpty) - throw new InvalidOperationException("The deque is empty."); - - return this.DoRemoveFromFront(); - } - - /// - /// Removes all items from this deque. - /// - public void Clear() - { - this.offset = 0; - this.Count = 0; - } - - [DebuggerNonUserCode] - sealed class DebugView - { - readonly Deque deque; - - public DebugView(Deque deque) - { - this.deque = deque; - } - - [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public T[] Items - { - get - { - var array = new T[this.deque.Count]; - ((ICollection)this.deque).CopyTo(array, 0); - return array; - } - } - } - } -} diff --git a/src/DotNetty.Common/ResourceLeakDetector.cs b/src/DotNetty.Common/ResourceLeakDetector.cs index bebc25958..34145e8c1 100644 --- a/src/DotNetty.Common/ResourceLeakDetector.cs +++ b/src/DotNetty.Common/ResourceLeakDetector.cs @@ -141,7 +141,13 @@ public IResourceLeakTracker Track(object obj) void ReportLeak(DefaultResourceLeak resourceLeak) { - string records = resourceLeak.ToString(); + if (!Logger.ErrorEnabled) + { + resourceLeak.Dispose(); + return; + } + + string records = resourceLeak.Dump(); if (this.reportedLeaks.TryAdd(records, true)) { if (records.Length == 0) @@ -179,21 +185,31 @@ sealed class DefaultResourceLeak : IResourceLeakTracker RecordEntry head; long droppedRecords; + readonly WeakReference gcNotice; public DefaultResourceLeak(ResourceLeakDetector owner, object referent) { Debug.Assert(referent != null); this.owner = owner; - if (owner.gcNotificationMap.TryGetValue(referent, out GCNotice existingNotice)) - { - existingNotice.Rearm(this); - } - else + GCNotice gcNotice; + do { - owner.gcNotificationMap.Add(referent, new GCNotice(this, referent)); + GCNotice gcNotice0 = null; + gcNotice = owner.gcNotificationMap.GetValue(referent, referent0 => + { + gcNotice0 = new GCNotice(referent0, owner); + return gcNotice0; + }); + if (gcNotice0 != null && gcNotice0 != gcNotice) + { + GC.SuppressFinalize(gcNotice0); + } } + while (!gcNotice.Arm(this, owner, referent)); + this.gcNotice = new WeakReference(gcNotice); this.head = RecordEntry.Bottom; + Record(); } public void Record() => this.Record0(null); @@ -213,7 +229,7 @@ void Record0(object hint) bool dropped; do { - if ((prevHead = oldHead = this.head) == null) + if ((prevHead = oldHead = Volatile.Read(ref this.head)) == null) { // already closed. return; @@ -244,40 +260,34 @@ void Record0(object hint) public bool Close(object trackedObject) { - if (this.owner.gcNotificationMap.TryGetValue(trackedObject, out GCNotice notice)) + if (gcNotice.TryGetTarget(out var notice)) { - // The close is called by byte buffer release, in this case - // we suppress the GCNotice finalize to prevent false positive - // report where the byte buffer instance gets reused by thread - // local cache and the existing GCNotice finalizer still holds - // the same byte buffer instance. - GC.SuppressFinalize(notice); - - Debug.Assert(this.owner.gcNotificationMap.Remove(trackedObject)); - Interlocked.Exchange(ref this.head, null); - return true; + if (notice.UnArm(this, owner, trackedObject)) + { + this.Dispose(); + return true; + } } return false; } - // This is called from GCNotice finalizer - internal void CloseFinal(object trackedObject) + // This is called from GCNotice finalizer + internal void CloseFinal() { - if (this.owner.gcNotificationMap.Remove(trackedObject) - && Volatile.Read(ref this.head) != null) + if (Volatile.Read(ref this.head) != null) { this.owner.ReportLeak(this); } } - public override string ToString() + public string Dump() { RecordEntry oldHead = Interlocked.Exchange(ref this.head, null); if (oldHead == null) { // Already closed - return string.Empty; + return string.Empty; } long dropped = Interlocked.Read(ref this.droppedRecords); @@ -314,7 +324,7 @@ public override string ToString() if (duped > 0) { buf.Append(": ") - .Append(dropped) + .Append(duped) .Append(" leak records were discarded because they were duplicates") .Append(StringUtil.Newline); } @@ -334,6 +344,11 @@ public override string ToString() buf.Length = buf.Length - StringUtil.Newline.Length; return buf.ToString(); } + + internal void Dispose() + { + Interlocked.Exchange(ref this.head, null); + } } // Record @@ -403,26 +418,81 @@ class GCNotice // Once the key dies, the dictionary automatically removes // the key/value entry. // - DefaultResourceLeak leak; + private readonly LinkedList leakList = new LinkedList(); object referent; - - public GCNotice(DefaultResourceLeak leak, object referent) + ResourceLeakDetector owner; + public GCNotice(object referent, ResourceLeakDetector owner) { - this.leak = leak; this.referent = referent; + this.owner = owner; } ~GCNotice() { - object trackedObject = this.referent; - this.referent = null; - this.leak.CloseFinal(trackedObject); + lock (this.leakList) + { + foreach (var leak in this.leakList) + { + leak.CloseFinal(); + } + this.leakList.Clear(); + + //Since we get here with finalizer, it's no needed to remove key from gcNotificationMap + + //this.referent = null; + this.owner = null; + } + } + + public bool Arm(DefaultResourceLeak leak, ResourceLeakDetector owner, object referent) + { + lock (this.leakList) + { + if (this.owner == null) + { + //Already disposed + return false; + } + Debug.Assert(owner == this.owner); + Debug.Assert(referent == this.referent); + + this.leakList.AddLast(leak); + return true; + } } - public void Rearm(DefaultResourceLeak newLeak) + public bool UnArm(DefaultResourceLeak leak, ResourceLeakDetector owner, object referent) { - DefaultResourceLeak oldLeak = Interlocked.Exchange(ref this.leak, newLeak); - oldLeak.CloseFinal(this.referent); + lock (this.leakList) + { + if (this.owner == null) + { + //Already disposed + return false; + } + Debug.Assert(owner == this.owner); + Debug.Assert(referent == this.referent); + + bool res = this.leakList.Remove(leak); + if (this.leakList.Count == 0) + { + // The close is called by byte buffer release, in this case + // we suppress the GCNotice finalize to prevent false positive + // report where the byte buffer instance gets reused by thread + // local cache and the existing GCNotice finalizer still holds + // the same byte buffer instance. + GC.SuppressFinalize(this); + + // Don't inline the variable, anything inside Debug.Assert() + // will be stripped out in Release builds + bool removed = this.owner.gcNotificationMap.Remove(this.referent); + Debug.Assert(removed); + + //this.referent = null; + this.owner = null; + } + return res; + } } } }