Skip to content

Commit 64d0d4a

Browse files
committed
Improve non-buffering strategy for FileStream
1 parent 86adef5 commit 64d0d4a

File tree

3 files changed

+14
-19
lines changed

3 files changed

+14
-19
lines changed

Src/IronPython.Modules/nt.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -883,8 +883,6 @@ public static void mkdir(CodeContext context, [NotNone] Bytes path, [ParamDictio
883883
public static void mkdir(CodeContext context, object? path, [ParamDictionary, NotNone] IDictionary<string, object> kwargs, [NotNone] params object[] args)
884884
=> mkdir(ConvertToFsString(context, path, nameof(path)), kwargs, args);
885885

886-
private const int DefaultBufferSize = 4096;
887-
888886
[Documentation("""
889887
open(path, flags, mode=511, *, dir_fd=None)
890888
@@ -926,6 +924,12 @@ public static object open(CodeContext/*!*/ context, [NotNone] string path, int f
926924
}
927925

928926
try {
927+
// FileStream buffer size must be >= 0 on .NET, and >= 1 on .NET Framework and Mono.
928+
// On .NET, buffer size 0 or 1 disables buffering.
929+
// On .NET Framework, buffer size 1 disables buffering.
930+
// On Mono, buffer size 1 makes writes of length >= 2 bypass the buffer.
931+
const int NoBuffering = 1;
932+
929933
FileMode fileMode = FileModeFromFlags(flags);
930934
FileAccess access = FileAccessFromFlags(flags);
931935
FileOptions options = FileOptionsFromFlags(flags);
@@ -940,15 +944,15 @@ public static object open(CodeContext/*!*/ context, [NotNone] string path, int f
940944
// open it again w/ just read access.
941945
fs = new FileStream(path, fileMode, FileAccess.Write, FileShare.None);
942946
fs.Close();
943-
s = fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, DefaultBufferSize, options);
947+
s = fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, NoBuffering, options);
944948
} else if (access == FileAccess.ReadWrite && fileMode == FileMode.Append) {
945949
// .NET doesn't allow Append w/ access != Write, so open the file w/ Write
946950
// and a secondary stream w/ Read, then seek to the end.
947-
s = fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite, DefaultBufferSize, options);
948-
rs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, DefaultBufferSize, options);
951+
s = fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite, NoBuffering, options);
952+
rs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, NoBuffering, options);
949953
rs.Seek(0L, SeekOrigin.End);
950954
} else {
951-
s = fs = new FileStream(path, fileMode, access, FileShare.ReadWrite, DefaultBufferSize, options);
955+
s = fs = new FileStream(path, fileMode, access, FileShare.ReadWrite, NoBuffering, options);
952956
}
953957
rs ??= s;
954958

Src/IronPython/Runtime/PythonFileManager.cs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,10 @@ internal sealed class StreamBox {
2929
private int _id = -1;
3030
private Stream _readStream;
3131
private Stream _writeStream;
32-
private readonly bool _writeNeedsFlush;
3332

3433
public StreamBox(Stream readStream, Stream writeStream) {
3534
_readStream = readStream;
3635
_writeStream = writeStream;
37-
38-
// a hack but it does improve perfomance a lot if flushing is not needed
39-
_writeNeedsFlush = writeStream switch {
40-
FileStream => true, // FileStream is buffered by default, used on Windows and mostly Mono
41-
PosixFileStream => false, // PosixFileStream is unbuffered, used on .NET/Posix
42-
UnixStream => false, // UnixStream is unbuffered, used sometimes on Mono
43-
_ => true // if in doubt, flush
44-
};
4536
}
4637

4738
public StreamBox(Stream stream) : this(stream, stream) { }
@@ -162,8 +153,8 @@ public int Write(IPythonBuffer buffer) {
162153
count = buffer.NumBytes();
163154
_writeStream.Write(bytes, 0, count);
164155
#endif
165-
if (_writeNeedsFlush) {
166-
_writeStream.Flush(); // IO at this level is not supposed to buffer so we need to call Flush.
156+
if (ClrModule.IsMono && count == 1) {
157+
_writeStream.Flush(); // IO at this level is not supposed to buffer so we need to call Flush (only needed on Mono)
167158
}
168159
if (!IsSingleStream) {
169160
_readStream.Seek(_writeStream.Position, SeekOrigin.Begin);

Tests/test_io_stdlib.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
## Run selected tests from test_io from StdLib
77
##
88

9-
from iptest import is_ironpython, is_mono, generate_suite, run_test
9+
from iptest import is_ironpython, is_mono, is_windows, generate_suite, run_test
1010

1111
import test.test_io
1212

@@ -100,7 +100,7 @@ def load_tests(loader, standard_tests, pattern):
100100
test.test_io.PyMiscIOTest('test_warn_on_dealloc_fd'), # AssertionError: ResourceWarning not triggered
101101
]
102102

103-
if is_mono:
103+
if is_mono or is_windows:
104104
failing_tests += [
105105
test.test_io.PyTextIOWrapperTest('test_seek_append_bom'), # OSError: [Errno -2146232800] Unable seek backward to overwrite data that previously existed in a file opened in Append mode.
106106
]

0 commit comments

Comments
 (0)