Skip to content

Commit 6a135cd

Browse files
committed
Introduced interfaces for buffered I/O
1 parent aa1371e commit 6a135cd

File tree

6 files changed

+158
-91
lines changed

6 files changed

+158
-91
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.Buffers;
2+
3+
namespace DotNext.Buffers;
4+
5+
/// <summary>
6+
/// Represents buffered reader or writer.
7+
/// </summary>
8+
public interface IBufferedChannel : IResettable, IDisposable
9+
{
10+
/// <summary>
11+
/// Gets buffer allocator.
12+
/// </summary>
13+
MemoryAllocator<byte>? Allocator { get; init; }
14+
15+
/// <summary>
16+
/// Gets the maximum size of the internal buffer.
17+
/// </summary>
18+
int MaxBufferSize { get; init; }
19+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
namespace DotNext.Buffers;
2+
3+
public interface IBufferedReader : IBufferedChannel
4+
{
5+
/// <summary>
6+
/// Gets unconsumed part of the buffer.
7+
/// </summary>
8+
ReadOnlyMemory<byte> Buffer { get; }
9+
10+
/// <summary>
11+
/// Advances read position.
12+
/// </summary>
13+
/// <param name="count">The number of consumed bytes.</param>
14+
/// <exception cref="ObjectDisposedException">The reader has been disposed.</exception>
15+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is larger than the length of <see cref="Buffer"/>.</exception>
16+
void Consume(int count);
17+
18+
/// <summary>
19+
/// Fetches the data from the underlying storage to the internal buffer.
20+
/// </summary>
21+
/// <param name="token">The token that can be used to cancel the operation.</param>
22+
/// <returns>
23+
/// <see langword="true"/> if the data has been copied from the underlying storage to the internal buffer;
24+
/// <see langword="false"/> if no more data to read.
25+
/// </returns>
26+
/// <exception cref="ObjectDisposedException">The reader has been disposed.</exception>
27+
/// <exception cref="InternalBufferOverflowException">Internal buffer has no free space.</exception>
28+
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
29+
ValueTask<bool> ReadAsync(CancellationToken token);
30+
31+
/// <summary>
32+
/// Reads the block of the memory.
33+
/// </summary>
34+
/// <param name="destination">The output buffer.</param>
35+
/// <param name="token">The token that can be used to cancel the operation.</param>
36+
/// <returns>The number of bytes copied to <paramref name="destination"/>.</returns>
37+
/// <exception cref="ObjectDisposedException">The reader has been disposed.</exception>
38+
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
39+
async ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken token)
40+
{
41+
var result = 0;
42+
for (int bytesRead; result < destination.Length; result += bytesRead, destination = destination.Slice(bytesRead))
43+
{
44+
Buffer.Span.CopyTo(destination.Span, out bytesRead);
45+
Consume(bytesRead);
46+
if (!await ReadAsync(token).ConfigureAwait(false))
47+
break;
48+
}
49+
50+
return result;
51+
}
52+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System.Buffers;
2+
3+
namespace DotNext.Buffers;
4+
5+
/// <summary>
6+
/// Represents buffered writer.
7+
/// </summary>
8+
public interface IBufferedWriter : IBufferedChannel, IBufferWriter<byte>
9+
{
10+
/// <summary>
11+
/// Marks the specified number of bytes in the buffer as produced.
12+
/// </summary>
13+
/// <param name="count">The number of produced bytes.</param>
14+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is larger than the length of <see cref="Buffer"/>.</exception>
15+
/// <exception cref="ObjectDisposedException">The writer has been disposed.</exception>
16+
void Produce(int count);
17+
18+
/// <summary>
19+
/// The remaining part of the internal buffer available for write.
20+
/// </summary>
21+
/// <remarks>
22+
/// The size of returned buffer may be less than or equal to <see cref="IBufferedChannel.MaxBufferSize"/>.
23+
/// </remarks>
24+
Memory<byte> Buffer { get; }
25+
26+
/// <summary>
27+
/// Flushes buffered data to the underlying storage.
28+
/// </summary>
29+
/// <param name="token">The token that can be used to cancel the operation.</param>
30+
/// <returns>The task representing asynchronous result.</returns>
31+
/// <exception cref="ObjectDisposedException">The writer has been disposed.</exception>
32+
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
33+
ValueTask WriteAsync(CancellationToken token);
34+
35+
/// <summary>
36+
/// Writes the data to the underlying storage through the buffer.
37+
/// </summary>
38+
/// <param name="input">The input data to write.</param>
39+
/// <param name="token">The token that can be used to cancel the operation.</param>
40+
/// <returns>The task representing asynchronous result.</returns>
41+
/// <exception cref="ObjectDisposedException">The object has been disposed.</exception>
42+
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
43+
async ValueTask WriteAsync(ReadOnlyMemory<byte> input, CancellationToken token)
44+
{
45+
for (int bytesWritten; !input.IsEmpty; input = input.Slice(bytesWritten))
46+
{
47+
input.Span.CopyTo(Buffer.Span, out bytesWritten);
48+
Produce(bytesWritten);
49+
await WriteAsync(token).ConfigureAwait(false);
50+
}
51+
}
52+
53+
/// <inheritdoc />
54+
void IBufferWriter<byte>.Advance(int count) => Produce(count);
55+
56+
/// <inheritdoc />
57+
Memory<byte> IBufferWriter<byte>.GetMemory(int sizeHint)
58+
{
59+
ArgumentOutOfRangeException.ThrowIfNegative(sizeHint);
60+
61+
var result = Buffer;
62+
return sizeHint <= result.Length ? result : throw new InsufficientMemoryException();
63+
}
64+
65+
/// <inheritdoc />
66+
Span<byte> IBufferWriter<byte>.GetSpan(int sizeHint)
67+
{
68+
ArgumentOutOfRangeException.ThrowIfNegative(sizeHint);
69+
70+
var result = Buffer.Span;
71+
return sizeHint <= result.Length ? result : throw new InsufficientMemoryException();
72+
}
73+
}

src/DotNext.IO/IO/FileReader.cs

Lines changed: 7 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace DotNext.IO;
1313
/// This class is not thread-safe. However, it's possible to share the same file
1414
/// handle across multiple readers and use dedicated reader in each thread.
1515
/// </remarks>
16-
public partial class FileReader : Disposable, IResettable
16+
public partial class FileReader : Disposable, IBufferedReader
1717
{
1818
private const int MinBufferSize = 16;
1919
private const int DefaultBufferSize = 4096;
@@ -55,9 +55,7 @@ public FileReader(FileStream source)
5555
FilePosition = source.Position;
5656
}
5757

58-
/// <summary>
59-
/// Gets or sets the buffer allocator.
60-
/// </summary>
58+
/// <inheritdoc cref="IBufferedChannel.Allocator"/>
6159
public MemoryAllocator<byte>? Allocator
6260
{
6361
get;
@@ -95,9 +93,7 @@ public long FilePosition
9593
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
9694
private int BufferLength => bufferEnd - bufferStart;
9795

98-
/// <summary>
99-
/// Gets unconsumed part of the buffer.
100-
/// </summary>
96+
/// <inheritdoc cref="IBufferedReader.Buffer"/>
10197
public ReadOnlyMemory<byte> Buffer => buffer.Memory[bufferStart..bufferEnd];
10298

10399
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
@@ -118,21 +114,14 @@ private ref readonly MemoryOwner<byte> EnsureBufferAllocated()
118114
/// </summary>
119115
public bool HasBufferedData => bufferStart < bufferEnd;
120116

121-
/// <summary>
122-
/// Gets the maximum possible amount of data that can be placed to the buffer.
123-
/// </summary>
124-
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> too small.</exception>
117+
/// <inheritdoc cref="IBufferedChannel.MaxBufferSize"/>
125118
public int MaxBufferSize
126119
{
127120
get => maxBufferSize;
128121
init => maxBufferSize = value >= MinBufferSize ? value : throw new ArgumentOutOfRangeException(nameof(value));
129122
}
130123

131-
/// <summary>
132-
/// Advances read position.
133-
/// </summary>
134-
/// <param name="count">The number of consumed bytes.</param>
135-
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is larger than the length of <see cref="Buffer"/>.</exception>
124+
/// <inheritdoc cref="IBufferedReader.Consume(int)"/>
136125
public void Consume(int count)
137126
{
138127
ObjectDisposedException.ThrowIf(IsDisposed, this);
@@ -200,17 +189,7 @@ public void Reset()
200189
buffer.Dispose();
201190
}
202191

203-
/// <summary>
204-
/// Reads the data from the file to the underlying buffer.
205-
/// </summary>
206-
/// <param name="token">The token that can be used to cancel the operation.</param>
207-
/// <returns>
208-
/// <see langword="true"/> if the data has been copied from the file to the internal buffer;
209-
/// <see langword="false"/> if no more data to read.
210-
/// </returns>
211-
/// <exception cref="ObjectDisposedException">The reader has been disposed.</exception>
212-
/// <exception cref="InternalBufferOverflowException">Internal buffer has no free space.</exception>
213-
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
192+
/// <inheritdoc cref="IBufferedReader.ReadAsync(CancellationToken)"/>
214193
public ValueTask<bool> ReadAsync(CancellationToken token = default)
215194
{
216195
return IsDisposed
@@ -283,14 +262,7 @@ private bool ReadCore()
283262
return count > 0;
284263
}
285264

286-
/// <summary>
287-
/// Reads the block of the memory.
288-
/// </summary>
289-
/// <param name="destination">The output buffer.</param>
290-
/// <param name="token">The token that can be used to cancel the operation.</param>
291-
/// <returns>The number of bytes copied to <paramref name="destination"/>.</returns>
292-
/// <exception cref="ObjectDisposedException">The reader has been disposed.</exception>
293-
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
265+
/// <inheritdoc cref="IBufferedReader.ReadAsync(Memory{byte}, CancellationToken)"/>
294266
public ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken token = default)
295267
{
296268
ValueTask<int> task;

src/DotNext.IO/IO/FileWriter.BufferWriter.cs

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/DotNext.IO/IO/FileWriter.cs

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace DotNext.IO;
1212
/// This class is not thread-safe. However, it's possible to share the same file
1313
/// handle across multiple writers and use dedicated writer in each thread.
1414
/// </remarks>
15-
public partial class FileWriter : Disposable, IFlushable, IResettable
15+
public partial class FileWriter : Disposable, IFlushable, IBufferedWriter
1616
{
1717
private const int MinBufferSize = 16;
1818
private const int DefaultBufferSize = 4096;
@@ -53,9 +53,7 @@ public FileWriter(FileStream destination)
5353
FilePosition = destination.Position;
5454
}
5555

56-
/// <summary>
57-
/// Gets or sets the buffer allocator.
58-
/// </summary>
56+
/// <inheritdoc cref="IBufferedChannel.Allocator"/>
5957
public MemoryAllocator<byte>? Allocator
6058
{
6159
get;
@@ -96,21 +94,14 @@ private void AssertState()
9694
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
9795
private Span<byte> BufferSpan => EnsureBufferAllocated().Span.Slice(bufferOffset);
9896

99-
/// <summary>
100-
/// Gets the maximum available buffer size.
101-
/// </summary>
102-
/// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/> too small.</exception>
97+
/// <inheritdoc cref="IBufferedChannel.MaxBufferSize"/>
10398
public int MaxBufferSize
10499
{
105100
get => maxBufferSize;
106101
init => maxBufferSize = value >= MinBufferSize ? value : throw new ArgumentOutOfRangeException(nameof(value));
107102
}
108103

109-
/// <summary>
110-
/// Marks the specified number of bytes in the buffer as produced.
111-
/// </summary>
112-
/// <param name="count">The number of produced bytes.</param>
113-
/// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is larger than the length of <see cref="Buffer"/>.</exception>
104+
/// <inheritdoc cref="IBufferedWriter.Produce(int)"/>
114105
public void Produce(int count)
115106
{
116107
ObjectDisposedException.ThrowIf(IsDisposed, this);
@@ -169,7 +160,7 @@ public bool HasBufferedData
169160
/// Gets or sets the cursor position within the file.
170161
/// </summary>
171162
/// <exception cref="ArgumentOutOfRangeException">The value is less than zero.</exception>
172-
/// <exception cref="InvalidOperationException">There is buffered data present. Call <see cref="ClearBuffer"/> or <see cref="WriteAsync(CancellationToken)"/> before changing the position.</exception>
163+
/// <exception cref="InvalidOperationException">There is buffered data present. Call <see cref="Reset()"/> or <see cref="WriteAsync(CancellationToken)"/> before changing the position.</exception>
173164
public long FilePosition
174165
{
175166
get => fileOffset;
@@ -203,13 +194,7 @@ private void Flush()
203194
Reset();
204195
}
205196

206-
/// <summary>
207-
/// Flushes buffered data to the file.
208-
/// </summary>
209-
/// <param name="token">The token that can be used to cancel the operation.</param>
210-
/// <returns>The task representing asynchronous result.</returns>
211-
/// <exception cref="ObjectDisposedException">The writer has been disposed.</exception>
212-
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
197+
/// <inheritdoc cref="IBufferedWriter.WriteAsync(CancellationToken)"/>
213198
public ValueTask WriteAsync(CancellationToken token = default)
214199
{
215200
if (IsDisposed)
@@ -272,14 +257,7 @@ private void WriteSlow(ReadOnlySpan<byte> input)
272257
}
273258
}
274259

275-
/// <summary>
276-
/// Writes the data to the file through the buffer.
277-
/// </summary>
278-
/// <param name="input">The input data to write.</param>
279-
/// <param name="token">The token that can be used to cancel the operation.</param>
280-
/// <returns>The task representing asynchronous result.</returns>
281-
/// <exception cref="ObjectDisposedException">The object has been disposed.</exception>
282-
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
260+
/// <inheritdoc cref="IBufferedWriter.WriteAsync(ReadOnlyMemory{byte}, CancellationToken)"/>
283261
public ValueTask WriteAsync(ReadOnlyMemory<byte> input, CancellationToken token = default)
284262
{
285263
ValueTask task;

0 commit comments

Comments
 (0)