Skip to content

Commit

Permalink
fixed IndexOutOfRangeException when creating child loggers multithrea…
Browse files Browse the repository at this point in the history
…ded - fixes #197 (#198)
  • Loading branch information
FreeAndNil authored Oct 18, 2024
1 parent ab16a6e commit 36ea5b4
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 103 deletions.
8 changes: 7 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,11 @@ dotnet_diagnostic.SYSLIB0050.severity = none
# SYSLIB0051: Type or member is obsolete
dotnet_diagnostic.SYSLIB0051.severity = none

# KR1010: Use correct class field name prefixes
dotnet_diagnostic.KR1010.severity = none
# KR1012: Classes should be sealed.
dotnet_diagnostic.KR1012.severity = none
dotnet_diagnostic.KR1012.severity = none
# KR1013: Name base classes correctly
dotnet_diagnostic.KR1013.severity = none
# KR1037: Use EnvironmentHelper instead of DateTime.
dotnet_diagnostic.KR1037.severity = none
146 changes: 86 additions & 60 deletions src/log4net.Tests/Hierarchy/HierarchyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/

using System;
using System.Threading.Tasks;
using System.Xml;
using log4net.Config;
using log4net.Repository;
Expand All @@ -29,27 +30,27 @@
namespace log4net.Tests.Hierarchy;

[TestFixture]
public class Hierarchy
public class HierarchyTest
{
[Test]
public void SetRepositoryPropertiesInConfigFile()
{
// LOG4NET-53: Allow repository properties to be set in the config file
XmlDocument log4NetConfig = new();
log4NetConfig.LoadXml(@"
<log4net>
<property>
<key value=""two-plus-two"" />
<value value=""4"" />
</property>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
</log4net>");
<log4net>
<property>
<key value=""two-plus-two"" />
<value value=""4"" />
</property>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
</log4net>");

ILoggerRepository rep = LogManager.CreateRepository(Guid.NewGuid().ToString());
XmlConfigurator.Configure(rep, log4NetConfig["log4net"]!);
Expand Down Expand Up @@ -99,18 +100,18 @@ public void LoggerNameCanConsistOfASingleDot()
{
XmlDocument log4NetConfig = new();
log4NetConfig.LoadXml(@"
<log4net>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
<logger name=""."">
<level value=""WARN"" />
</logger>
</log4net>");
<log4net>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
<logger name=""."">
<level value=""WARN"" />
</logger>
</log4net>");

ILoggerRepository rep = LogManager.CreateRepository(Guid.NewGuid().ToString());
XmlConfigurator.Configure(rep, log4NetConfig["log4net"]!);
Expand All @@ -121,18 +122,18 @@ public void LoggerNameCanConsistOfASingleNonDot()
{
XmlDocument log4NetConfig = new();
log4NetConfig.LoadXml(@"
<log4net>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
<logger name=""L"">
<level value=""WARN"" />
</logger>
</log4net>");
<log4net>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
<logger name=""L"">
<level value=""WARN"" />
</logger>
</log4net>");

ILoggerRepository rep = LogManager.CreateRepository(Guid.NewGuid().ToString());
XmlConfigurator.Configure(rep, log4NetConfig["log4net"]!);
Expand All @@ -143,18 +144,18 @@ public void LoggerNameCanContainSequenceOfDots()
{
XmlDocument log4NetConfig = new();
log4NetConfig.LoadXml(@"
<log4net>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
<logger name=""L..M"">
<level value=""WARN"" />
</logger>
</log4net>");
<log4net>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
<logger name=""L..M"">
<level value=""WARN"" />
</logger>
</log4net>");

ILoggerRepository rep = LogManager.CreateRepository(Guid.NewGuid().ToString());
XmlConfigurator.Configure(rep, log4NetConfig["log4net"]!);
Expand All @@ -169,19 +170,44 @@ public void CreateNestedLoggersInReverseOrder()
{
XmlDocument log4NetConfig = new();
log4NetConfig.LoadXml(@"
<log4net>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
</log4net>");
<log4net>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
</log4net>");

ILoggerRepository rep = LogManager.CreateRepository(Guid.NewGuid().ToString());
XmlConfigurator.Configure(rep, log4NetConfig["log4net"]!);
Assert.AreEqual("A.B.C", rep.GetLogger("A.B.C").Name);
Assert.AreEqual("A.B", rep.GetLogger("A.B").Name);
}
}

/// <summary>
/// https://github.com/apache/logging-log4net/issues/197
/// IndexOutOfRangeException when creating child loggers multithreaded
/// </summary>
[Test]
public void CreateChildLoggersMultiThreaded()
{
XmlDocument log4NetConfig = new();
log4NetConfig.LoadXml(@"
<log4net>
<appender name=""StringAppender"" type=""log4net.Tests.Appender.StringAppender, log4net.Tests"">
<layout type=""log4net.Layout.SimpleLayout"" />
</appender>
<root>
<level value=""ALL"" />
<appender-ref ref=""StringAppender"" />
</root>
</log4net>");

ILoggerRepository rep = LogManager.CreateRepository(Guid.NewGuid().ToString());
XmlConfigurator.Configure(rep, log4NetConfig["log4net"]!);

Parallel.For(0, 100, i => Assert.AreEqual($"A.{i}", rep.GetLogger($"A.{i}").Name));
}
}
34 changes: 15 additions & 19 deletions src/log4net/Repository/Hierarchy/Hierarchy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,24 @@ namespace log4net.Repository.Hierarchy;
/// </summary>
/// <remarks>
/// <para>
/// A <see cref="Hierarchy.LoggerCreatedEvent"/> event is raised every time a
/// <see cref="Logger"/> is created.
/// A <see cref="Hierarchy.LoggerCreatedEvent"/> event is raised every time a <see cref="Logger"/> is created.
/// </para>
/// </remarks>
public class LoggerCreationEventArgs : EventArgs
/// <param name="log">The <see cref="Logger"/> that has been created.</param>
public class LoggerCreationEventArgs(Logger log) : EventArgs
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="log">The <see cref="Logger"/> that has been created.</param>
public LoggerCreationEventArgs(Logger log) => Logger = log;

/// <summary>
/// Gets the <see cref="Logger"/> that has been created.
/// </summary>
public Logger Logger { get; }
public Logger Logger { get; } = log;
}

/// <summary>
/// Hierarchical organization of loggers
/// </summary>
/// <remarks>
/// <para>
/// <i>The casual user should not have to deal with this class
/// directly.</i>
/// <i>The casual user should not have to deal with this class directly.</i>
/// </para>
/// <para>
/// This class is specialized in retrieving loggers by name and also maintaining the logger
Expand Down Expand Up @@ -343,9 +336,10 @@ public override void Log(LoggingEvent logEvent)
/// The list returned is unordered but does not contain duplicates.
/// </para>
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0305:Simplify collection initialization")]
public override IAppender[] GetAppenders()
{
var appenderList = new HashSet<IAppender>();
HashSet<IAppender> appenderList = [];

CollectAppenders(appenderList, Root);

Expand Down Expand Up @@ -407,7 +401,7 @@ void IBasicRepositoryConfigurator.Configure(params IAppender[] appenders)
/// </remarks>
protected void BasicRepositoryConfigure(params IAppender[] appenders)
{
var configurationMessages = new List<LogLog>();
List<LogLog> configurationMessages = [];

using (new LogLog.LogReceivedAdapter(configurationMessages))
{
Expand Down Expand Up @@ -444,7 +438,7 @@ protected void BasicRepositoryConfigure(params IAppender[] appenders)
/// </remarks>
protected void XmlRepositoryConfigure(System.Xml.XmlElement element)
{
var configurationMessages = new List<LogLog>();
List<LogLog> configurationMessages = [];

using (new LogLog.LogReceivedAdapter(configurationMessages))
{
Expand Down Expand Up @@ -516,7 +510,7 @@ public Logger GetLogger(string name, ILoggerFactory factory)
name.EnsureNotNull();
factory.EnsureNotNull();

var key = new LoggerKey(name);
LoggerKey key = new(name);

const int maxRetries = 5;
for (int i = 0; i < maxRetries; i++)
Expand Down Expand Up @@ -631,7 +625,7 @@ private void UpdateParents(Logger log)
{
string substr = name.Substring(0, i);

var key = new LoggerKey(substr);
LoggerKey key = new(substr);
_loggers.TryGetValue(key, out object? node);

// Create a provision node for a future parent.
Expand Down Expand Up @@ -693,9 +687,11 @@ private void UpdateParents(Logger log)
/// c's parent field to log.
/// </para>
/// </remarks>
private static void UpdateChildren(ProvisionNode pn, Logger log)
private static void UpdateChildren(ProvisionNode provisionNode, Logger log)
{
foreach (Logger childLogger in pn)
provisionNode.ForEach(Update, log);

static void Update(Logger childLogger, Logger log)
{
// Unless this child already points to a correct (lower) parent,
// make log.Parent point to childLogger.Parent and childLogger.Parent to log.
Expand Down
37 changes: 33 additions & 4 deletions src/log4net/Repository/Hierarchy/ProvisionNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
//
#endregion

using System;
using System.Collections;
using System.Collections.Generic;

namespace log4net.Repository.Hierarchy;
Expand All @@ -36,14 +38,41 @@ namespace log4net.Repository.Hierarchy;
/// </remarks>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
internal sealed class ProvisionNode : List<Logger>
internal sealed class ProvisionNode
{
private readonly List<Logger> _loggers;

/// <summary>
/// Create a new provision node with child node
/// </summary>
/// <param name="log">A child logger to add to this node.</param>
internal ProvisionNode(Logger log)
internal ProvisionNode(Logger log) => _loggers = [log];

/// <summary>
/// Add a <see cref="Logger"/> to the internal List
/// </summary>
/// <param name="log">Logger</param>
internal void Add(Logger log)
{
lock (((IList)_loggers).SyncRoot)
{
_loggers.Add(log);
}
}

/// <summary>
/// Calls <paramref name="callback"/> for each logger in the internal list
/// </summary>
/// <param name="callback">Callback to execute</param>
/// <param name="parent">Parant logger</param>
internal void ForEach(Action<Logger, Logger> callback, Logger parent)
{
Add(log);
lock (((IList)_loggers).SyncRoot)
{
foreach (Logger log in _loggers)
{
callback(log, parent);
}
}
}
}
}
Loading

0 comments on commit 36ea5b4

Please sign in to comment.