blob: 3ae5ae1f57f2edf4bc7dc4387bbeed0c2f87c581 [file] [log] [blame]
#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
using log4net.Core;
namespace log4net.Util;
/// <summary>
/// A fixed size rolling buffer of logging events.
/// </summary>
/// <remarks>
/// <para>
/// An array backed fixed size leaky bucket.
/// </para>
/// </remarks>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
public class CyclicBuffer
{
private readonly object _syncRoot = new();
/// <summary>
/// Constructor
/// </summary>
/// <param name="maxSize">The maximum number of logging events in the buffer.</param>
/// <remarks>
/// <para>
/// Initializes a new instance of the <see cref="CyclicBuffer" /> class with
/// the specified maximum number of buffered logging events.
/// </para>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="maxSize"/> argument is not a positive integer.</exception>
public CyclicBuffer(int maxSize)
{
if (maxSize < 1)
{
throw SystemInfo.CreateArgumentOutOfRangeException(nameof(maxSize), maxSize, $"Parameter: {nameof(maxSize)}, Value: [{maxSize}] out of range. A positive integer is required.");
}
_maxSize = maxSize;
_events = new LoggingEvent[maxSize];
_first = 0;
_last = 0;
_numElems = 0;
}
/// <summary>
/// Appends a <paramref name="loggingEvent"/> to the buffer.
/// </summary>
/// <param name="loggingEvent">The event to append to the buffer.</param>
/// <returns>The event discarded from the buffer, if the buffer is full, otherwise <c>null</c>.</returns>
/// <remarks>
/// <para>
/// Append an event to the buffer. If the buffer still contains free space then
/// <c>null</c> is returned. If the buffer is full then an event will be dropped
/// to make space for the new event, the dropped event is returned.
/// </para>
/// </remarks>
public LoggingEvent? Append(LoggingEvent loggingEvent)
{
loggingEvent.EnsureNotNull();
lock (_syncRoot)
{
// save the discarded event
LoggingEvent? discardedLoggingEvent = _events[_last];
// overwrite the last event position
_events[_last] = loggingEvent;
if (++_last == _maxSize)
{
_last = 0;
}
if (_numElems < _maxSize)
{
_numElems++;
}
else if (++_first == _maxSize)
{
_first = 0;
}
if (_numElems < _maxSize)
{
// Space remaining
return null;
}
// Buffer is full and discarding an event
return discardedLoggingEvent;
}
}
/// <summary>
/// Get and remove the oldest event in the buffer.
/// </summary>
/// <returns>The oldest logging event in the buffer</returns>
/// <remarks>
/// <para>
/// Gets the oldest (first) logging event in the buffer and removes it
/// from the buffer.
/// </para>
/// </remarks>
public LoggingEvent? PopOldest()
{
lock (_syncRoot)
{
LoggingEvent? ret = null;
if (_numElems > 0)
{
_numElems--;
ret = _events[_first];
_events[_first] = null;
if (++_first == _maxSize)
{
_first = 0;
}
}
return ret;
}
}
/// <summary>
/// Pops all the logging events from the buffer into an array.
/// </summary>
/// <returns>An array of all the logging events in the buffer.</returns>
/// <remarks>
/// <para>
/// Get all the events in the buffer and clear the buffer.
/// </para>
/// </remarks>
public LoggingEvent[] PopAll()
{
lock (_syncRoot)
{
LoggingEvent[] ret = new LoggingEvent[_numElems];
if (_numElems > 0)
{
if (_first < _last)
{
Array.Copy(_events, _first, ret, 0, _numElems);
}
else
{
Array.Copy(_events, _first, ret, 0, _maxSize - _first);
Array.Copy(_events, 0, ret, _maxSize - _first, _last);
}
}
Clear();
return ret;
}
}
/// <summary>
/// Clear the buffer
/// </summary>
/// <remarks>
/// <para>
/// Clear the buffer of all events. The events in the buffer are lost.
/// </para>
/// </remarks>
public void Clear()
{
lock (_syncRoot)
{
// Set all the elements to null
Array.Clear(_events, 0, _events.Length);
_first = 0;
_last = 0;
_numElems = 0;
}
}
#if RESIZABLE_CYCLIC_BUFFER
/// <summary>
/// Resizes the cyclic buffer to <paramref name="newSize"/>.
/// </summary>
/// <param name="newSize">The new size of the buffer.</param>
/// <remarks>
/// <para>
/// Resize the cyclic buffer. Events in the buffer are copied into
/// the newly sized buffer. If the buffer is shrunk and there are
/// more events currently in the buffer than the new size of the
/// buffer then the newest events will be dropped from the buffer.
/// </para>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">The <paramref name="newSize"/> argument is not a positive integer.</exception>
public void Resize(int newSize)
{
lock (syncRoot)
{
if (newSize < 0)
{
throw SystemInfo.CreateArgumentOutOfRangeException(nameof(newSize), newSize, $"Parameter: {nameof(newSize)}, Value: [{newSize}] out of range. A positive integer is required.");
}
if (newSize == numElems)
{
return; // nothing to do
}
LoggingEvent[] temp = new LoggingEvent[newSize];
int loopLen = (newSize < numElems) ? newSize : numElems;
for(int i = 0; i < loopLen; i++)
{
temp[i] = events[first];
events[first] = null;
if (++first == numElems)
{
first = 0;
}
}
events = temp;
first = 0;
numElems = loopLen;
maxSize = newSize;
if (loopLen == newSize)
{
last = 0;
}
else
{
last = loopLen;
}
}
}
#endif
/// <summary>
/// Gets the <paramref name="i"/>th oldest event currently in the buffer.
/// </summary>
/// <remarks>
/// <para>
/// If <paramref name="i"/> is outside the range 0 to the number of events
/// currently in the buffer, then <c>null</c> is returned.
/// </para>
/// </remarks>
public LoggingEvent? this[int i]
{
get
{
lock (_syncRoot)
{
if (i < 0 || i >= _numElems)
{
return null;
}
return _events[(_first + i) % _maxSize];
}
}
}
/// <summary>
/// Gets the maximum size of the buffer.
/// </summary>
/// <value>The maximum size of the buffer.</value>
/// <remarks>
/// <para>
/// Gets the maximum size of the buffer
/// </para>
/// </remarks>
public int MaxSize
{
get
{
lock (_syncRoot)
{
return _maxSize;
}
}
#if RESIZABLE_CYCLIC_BUFFER
set
{
/// Setting the MaxSize will cause the buffer to resize.
Resize(value);
}
#endif
}
/// <summary>
/// Gets the number of logging events in the buffer.
/// </summary>
/// <value>The number of logging events in the buffer.</value>
/// <remarks>
/// <para>
/// This number is guaranteed to be in the range 0 to <see cref="MaxSize"/>
/// (inclusive).
/// </para>
/// </remarks>
public int Length
{
get
{
lock (_syncRoot)
{
return _numElems;
}
}
}
private readonly LoggingEvent?[] _events;
private int _first;
private int _last;
private int _numElems;
private readonly int _maxSize;
}