Skip to content

Commit 04779e2

Browse files
paulirwinclaude
andauthored
Fix concurrent-write race in SafeTextWriterWrapper, apache#1246 (apache#1262)
* Fix concurrent-write race in SafeTextWriterWrapper, apache#1246 Wrap the inner TextWriter in TextWriter.Synchronized so that concurrent writes from IndexSearcher executor threads to diagnostic info streams (FieldCache, IndexWriter, SegmentInfos, CheckIndex) do not corrupt non-thread-safe writers such as StringWriter. This matches the thread-safety of Java's PrintStream, which synchronizes at the println boundary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add [TestFixture] and [LuceneNetSpecific] to TestSafeTextWriterWrapper Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2599c2e commit 04779e2

2 files changed

Lines changed: 42 additions & 1 deletion

File tree

src/Lucene.Net.Tests/Support/IO/TestSafeTextWriterWrapper.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
using Lucene.Net.Attributes;
12
using Lucene.Net.Util;
23
using NUnit.Framework;
34
using System;
45
using System.IO;
6+
using System.Threading.Tasks;
57

68
namespace Lucene.Net.Support.IO
79
{
@@ -22,6 +24,8 @@ namespace Lucene.Net.Support.IO
2224
* limitations under the License.
2325
*/
2426

27+
[TestFixture]
28+
[LuceneNetSpecific]
2529
public class TestSafeTextWriterWrapper : LuceneTestCase
2630
{
2731
[Test]
@@ -60,5 +64,33 @@ public void TestWriteLine()
6064
Assert.DoesNotThrow(() => safe.WriteLine("Testing"));
6165
Assert.DoesNotThrow(() => safe.WriteLine("Testing"));
6266
}
67+
68+
/// <summary>
69+
/// LUCENENET specific. When <see cref="SafeTextWriterWrapper"/> wraps a
70+
/// non-thread-safe <see cref="TextWriter"/> (such as <see cref="StringWriter"/>),
71+
/// concurrent writes from multiple threads must not throw. This reproduces
72+
/// https://github.com/apache/lucenenet/issues/1246 where the <see cref="FieldCache"/>
73+
/// info stream was written concurrently from <see cref="Search.IndexSearcher"/>'s
74+
/// executor threads, causing <see cref="ArgumentException"/> ("Destination is too
75+
/// short") from <see cref="System.Text.StringBuilder"/>.
76+
/// </summary>
77+
[Test]
78+
public void TestConcurrentWrites()
79+
{
80+
using var wrapped = new StringWriter();
81+
using var safe = new SafeTextWriterWrapper(wrapped);
82+
83+
const int threadCount = 8;
84+
const int iterationsPerThread = 2000;
85+
const string line = "WARNING: new FieldCache insanity created. Details: some long-ish message for buffer growth.";
86+
87+
Parallel.For(0, threadCount, _ =>
88+
{
89+
for (int i = 0; i < iterationsPerThread; i++)
90+
{
91+
safe.WriteLine(line);
92+
}
93+
});
94+
}
6395
}
6496
}

src/Lucene.Net/Support/IO/SafeTextWriterWrapper.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,16 @@ internal class SafeTextWriterWrapper : TextWriter
4444

4545
public SafeTextWriterWrapper(TextWriter textWriter)
4646
{
47-
this.textWriter = textWriter ?? throw new ArgumentNullException(nameof(textWriter));
47+
if (textWriter is null)
48+
throw new ArgumentNullException(nameof(textWriter));
49+
50+
// LUCENENET specific: Wrap in a synchronized TextWriter so that concurrent
51+
// writes from multiple threads (e.g., IndexSearcher executor threads writing
52+
// to FieldCache.InfoStream) do not corrupt non-thread-safe writers such as
53+
// StringWriter. This matches the thread-safety of Java's PrintStream, which
54+
// synchronizes at the println boundary. See
55+
// https://github.com/apache/lucenenet/issues/1246.
56+
this.textWriter = TextWriter.Synchronized(textWriter);
4857
}
4958

5059
public override Encoding Encoding => Run(() => textWriter.Encoding);

0 commit comments

Comments
 (0)