FreeAndNil commented on code in PR #319:
URL: https://github.com/apache/logging-log4net/pull/319#discussion_r4054378994


##########
src/Directory.Build.props:
##########
@@ -34,6 +34,7 @@
     <NUnitAnalyzersPackageVersion>4.3.0</NUnitAnalyzersPackageVersion>
     <NUnitPackageVersion>4.2.2</NUnitPackageVersion>
     <NUnit3TestAdapterPackageVersion>4.6.0</NUnit3TestAdapterPackageVersion>
+    <PeanutButterUtilsPackageVersion>2.0.63</PeanutButterUtilsPackageVersion>

Review Comment:
   Bumped to 3.0.437. Still targets net462 and netstandard2.0, and 
`AutoTempFolder` is unchanged.
   



##########
src/log4net/Appender/FileAppender.cs:
##########
@@ -1358,6 +1368,40 @@ protected virtual void SetQWForFiles(TextWriter writer)
   /// </remarks>
   protected static string ConvertToFullPath(string path) => 
SystemInfo.ConvertToFullPath(path);
 
+  /// <summary>
+  /// Names the mutex that serialises <paramref name="path"/> between 
processes, with
+  /// <paramref name="suffix"/> telling one mutex over the same file from 
another.
+  /// </summary>
+  /// <remarks>
+  /// The flattened path, as earlier versions computed it. Only a name the 
platform rejects is
+  /// hashed: Unix stops at <see cref="MaxMutexNameLength"/>, Windows has no 
limit. Unprefixed, so
+  /// on Windows it coordinates one session.
+  /// </remarks>
+  internal static string MutexNameForPath(string path, string suffix)
+    => MutexNameForPath(path, suffix, SystemInfo.IsWindows ? null : 
MaxMutexNameLength);
+
+  /// <summary>Takes the limit rather than deciding it, so both branches are 
testable anywhere.</summary>
+  private static string MutexNameForPath(string path, string suffix, int? 
maxLength)
+  {
+    string name = path.EnsureNotNull()
+      .Replace("\\", "_")
+      .Replace(":", "_")
+      .Replace("/", "_") + suffix;
+
+    if (maxLength is null || name.Length <= maxLength)

Review Comment:
   Right, and measured: 104 characters at 304 bytes throws, 255 ASCII 
characters does not. The cap uses
   `Encoding.UTF8.GetByteCount` now, pinned by 
`AMultibytePathIsMeasuredInBytes`.
   



##########
src/log4net/Appender/RollingFileAppender.cs:
##########
@@ -556,9 +574,16 @@ protected virtual void AdjustFileBeforeAppend()
         }
       }
 
-      if (_rollSize && (File is not null) && 
((CountingQuietTextWriter)QuietWriter!).Count >= MaxFileSize)
+      if (_rollSize && (File is not null) && CountingWriter.Count >= 
MaxFileSize)
       {
-        RollOverSize();
+        if (_pendingRename is null)
+        {
+          RollOverSize();
+        }
+        else if (CountingWriter.Count >= _pendingRename.RetryAtCount)

Review Comment:
   Right. The check now runs ahead of the size roll and independently of it, 
paced by `MaxFileSize`.
   Test: `AFailedTimeRenameIsRetriedWithoutSizeRolling`.
   



##########
src/log4net/Appender/RollingFileAppender.cs:
##########
@@ -1353,7 +1466,11 @@ protected virtual void RollOverRenameFiles(string 
baseFileName)
       CurrentSizeRollBackups++;
 
       // Rename fileName to fileName.1
-      RollFile(baseFileName, CombinePath(baseFileName, ".1"));
+      if (!TryRollFile(baseFileName, CombinePath(baseFileName, ".1")))
+      {
+        CurrentSizeRollBackups--;
+        RecordFailedBaseRename(baseFileName, CombinePath(baseFileName, ".1"), 
wasBackupCountReverted: true);

Review Comment:
   Right, and the one path the fix missed. Solved a step lower: `OpenFile` 
never truncates a file a
   pending rename left behind, whatever `AppendToFile` says, which also covers 
later reopens and
   mutates no user setting. Test: 
`AFailedStartupRollKeepsTheFileItCouldNotMove`.
   



##########
src/changelog/3.5.0/319-lock-level-underflow.xml:
##########
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns="https://logging.apache.org/xml/ns";
+       xsi:schemaLocation="https://logging.apache.org/xml/ns 
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="fixed">
+  <issue id="319" link="https://github.com/apache/logging-log4net/pull/319"/>
+  <description format="asciidoc">Stop the file lock counter going negative. 
Writing the footer,
+  closing the writer and opening the file released the lock even when 
acquiring it had failed, and a
+  negative count made every later acquisition fail. The footer and close paths 
share one helper now,
+  which releases only what it took; opening keeps its own acquire, because 
wrapping an unlocked
+  stream throws. Nothing was lost by this, because the appender reopens the 
file on the next
+  event (audit da18b6fd-f032, fixed by @FreeAndNil)</description>

Review Comment:
   Declined. The `<issue>` element above the description already carries the 
link. `CLAUDE.md` said
   otherwise, which is where the older entries come from; the rule is corrected 
instead.
   



##########
src/changelog/3.5.0/319-mutex-name-length.xml:
##########
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns="https://logging.apache.org/xml/ns";
+       xsi:schemaLocation="https://logging.apache.org/xml/ns 
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="fixed">
+  <issue id="319" link="https://github.com/apache/logging-log4net/pull/319"/>
+  <description format="asciidoc">Keep logging to a deep path on Unix. Both the 
rolling lock and the
+  inter-process file lock name their mutex after the log file, and Unix 
rejects a name longer than
+  255 characters, throwing out of `ActivateOptions` and taking the appender 
with it before anything
+  was written. Such a name is replaced by a hash of the path now. Windows 
enforces no
+  length limit at all, and names are left alone there, so the only ones that 
change are the ones
+  that used to throw and exclusion against an older version is nowhere 
affected (audit
+  da18b6fd-f010, da18b6fd-f031, fixed by @FreeAndNil)</description>

Review Comment:
   Declined. The `<issue>` element above the description already carries the 
link. `CLAUDE.md` said
   otherwise, which is where the older entries come from; the rule is corrected 
instead.
   



##########
src/changelog/3.5.0/319-mutex-resolved-path.xml:
##########
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns="https://logging.apache.org/xml/ns";
+       xsi:schemaLocation="https://logging.apache.org/xml/ns 
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="fixed">
+  <issue id="319" link="https://github.com/apache/logging-log4net/pull/319"/>
+  <description format="asciidoc">Let two processes that spell the log path 
differently share one
+  inter-process file lock. `InterProcessLock` named its mutex after the 
configured path before that
+  path was resolved, so a relative and an absolute spelling of one file took 
two different mutexes
+  and excluded nothing. The name comes from the resolved path now. That 
resolves a relative path and
+  nothing else: symbolic links, hard links, 8.3 short names, letter case and 
UNC versus mapped-drive
+  spellings still produce different names. The name is also unprefixed, which 
on Windows makes it
+  per-session, so a service and an interactive process have never coordinated 
through it (audit
+  da18b6fd-f031, fixed by @FreeAndNil)</description>

Review Comment:
   Declined. The `<issue>` element above the description already carries the 
link. `CLAUDE.md` said
   otherwise, which is where the older entries come from; the rule is corrected 
instead.
   



##########
src/changelog/3.5.0/319-rollover-keeps-events.xml:
##########
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xmlns="https://logging.apache.org/xml/ns";
+       xsi:schemaLocation="https://logging.apache.org/xml/ns 
https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="fixed">
+  <issue id="319" link="https://github.com/apache/logging-log4net/pull/319"/>
+  <description format="asciidoc">Keep the log file when a rollover cannot 
rename it. The failed rename
+  was reported and the file then reopened without appending, which destroyed 
everything it held; a
+  reader holding the file without `FILE_SHARE_DELETE`, such as a backup or 
antivirus agent, is enough
+  to cause it. The file is appended to now. Only the rename that failed is 
retried, once per
+  `MaxFileSize` of growth, which is the cadence a working rollover would have 
had, because a full
+  retry would shift the numbered backups again and lose the oldest one every 
time; the file
+  therefore grows past `MaxFileSize` for as long as the rename keeps failing 
(audit da18b6fd-f036,
+  fixed by @FreeAndNil)</description>

Review Comment:
   Declined. The `<issue>` element above the description already carries the 
link. `CLAUDE.md` said
   otherwise, which is where the older entries come from; the rule is corrected 
instead.
   



##########
src/log4net.Tests/Appender/FileAppenderMutexNameTest.cs:
##########
@@ -0,0 +1,208 @@
+#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 System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+
+using log4net.Appender;
+using log4net.Core;
+using log4net.Layout;
+using log4net.Util;
+
+using NUnit.Framework;
+
+using PeanutButter.Utils;
+
+namespace log4net.Tests.Appender;
+
+/// <summary>The mutex name the file lock and the rolling lock derive from the 
log file path.</summary>
+[TestFixture]
+public sealed class FileAppenderMutexNameTest
+{
+  /// <summary>Records what the appender had resolved by the time the locking 
model was activated.</summary>
+  private sealed class RecordingLock : FileAppender.LockingModelBase
+  {
+    internal string? FileAtActivation { get; private set; }
+
+    public override void ActivateOptions() => FileAtActivation = 
CurrentAppender?.File;
+
+    public override Stream? AcquireLock() => Stream.Null;
+
+    public override void ReleaseLock()
+    { }
+
+    public override void OpenFile(string filename, bool append, Encoding 
encoding)
+    { }
+
+    public override void CloseFile()
+    { }
+
+    public override void OnClose()
+    { }

Review Comment:
   Done, every override in both fakes.
   



##########
src/log4net.Tests/Appender/LockingStreamTest.cs:
##########
@@ -0,0 +1,143 @@
+#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 System.IO;
+using System.Reflection;
+using System.Text;
+
+using log4net.Appender;
+
+using NUnit.Framework;
+
+namespace log4net.Tests.Appender;
+
+/// <summary>The recursion counter inside the private 
<c>FileAppender.LockingStream</c>.</summary>
+[TestFixture]
+public sealed class LockingStreamTest
+{
+  /// <summary>Hands out a stream only when told to, so a failed acquisition 
can be staged.</summary>
+  private sealed class SwitchableLock : FileAppender.LockingModelBase
+  {
+    internal bool CanAcquire { get; set; }
+
+    internal int ReleaseCount { get; private set; }
+
+    public override Stream? AcquireLock() => CanAcquire ? Stream.Null : null;
+
+    public override void ReleaseLock() => ReleaseCount++;
+
+    public override void OpenFile(string filename, bool append, Encoding 
encoding)
+    { }
+
+    public override void CloseFile()
+    { }
+
+    public override void ActivateOptions()
+    { }
+
+    public override void OnClose()
+    { }

Review Comment:
   Done, every override in both fakes.
   



##########
src/log4net/Appender/RollingFileAppender.cs:
##########
@@ -1298,7 +1346,72 @@ protected void RollOverSize()
     }
 
     // This will also close the file. This is OK since multiple close 
operations are safe.
-    SafeOpenFile(_baseFileName!, false);
+    // A failed rename leaves the file in place; appending keeps what it holds.
+    SafeOpenFile(_baseFileName!, ShouldAppendAfterFailedRoll());
+
+    if (_pendingRename is not null)
+    {
+      ScheduleRollRetry();
+      // The failing rename already reported, and OnlyOnceErrorHandler 
silences the handler after
+      // the first report, so this one goes through LogLog to survive.
+      LogLog.Error(_declaringType,
+        $"Rolling {_pendingRename.From} failed, so it is kept and appended to. 
Only that rename is "
+        + "retried, once per MaxFileSize of growth, so the backups are left 
alone.");

Review Comment:
   Done. Worth knowing what it costs: the newlines reach the log and only the 
first line carries the
   `log4net:ERROR` prefix. Accepted, and `CLAUDE.md` now says so.
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to