@@ -24,6 +24,7 @@ public class HttpResponsePipeWriter : TextWriter
24
24
25
25
public override Encoding Encoding { get ; }
26
26
27
+ private int _uncommittedBytes = 0 ;
27
28
private bool _disposed ;
28
29
29
30
public HttpResponsePipeWriter (
@@ -38,122 +39,72 @@ public HttpResponsePipeWriter(
38
39
39
40
public override void Write ( char value )
40
41
{
41
- if ( _disposed )
42
- {
43
- throw new ObjectDisposedException ( nameof ( HttpResponseStreamWriter ) ) ;
44
- }
45
-
46
42
_singleCharArray [ 0 ] = value ;
47
- var span = new Span < char > ( _singleCharArray , 0 , 1 ) ;
48
- Write ( span ) ;
43
+ WriteInternal ( _singleCharArray . AsSpan ( 0 , 1 ) ) ;
49
44
}
50
45
51
46
public override void Write ( char [ ] values , int index , int count )
52
47
{
53
- if ( _disposed )
54
- {
55
- throw new ObjectDisposedException ( nameof ( HttpResponseStreamWriter ) ) ;
56
- }
57
-
58
- if ( values == null || count == 0 )
48
+ if ( values == null )
59
49
{
60
50
return ;
61
51
}
62
52
63
- var value = new Span < char > ( values , index , count ) ;
64
- Write ( value ) ;
53
+ WriteInternal ( values . AsSpan ( index , count ) ) ;
65
54
}
66
55
67
56
public override void Write ( ReadOnlySpan < char > value )
68
57
{
69
- if ( _disposed )
70
- {
71
- throw new ObjectDisposedException ( nameof ( HttpResponseStreamWriter ) ) ;
72
- }
73
-
74
58
if ( value == null )
75
59
{
76
60
return ;
77
61
}
78
62
79
- var length = _encoder . GetByteCount ( value , false ) ;
80
- var buffer = _writer . GetSpan ( length ) ;
81
- _encoder . GetBytes ( value , buffer , false ) ;
82
- _writer . Advance ( length ) ;
63
+ WriteInternal ( value ) ;
83
64
}
84
65
85
66
public override void Write ( string ? value )
86
67
{
87
- if ( _disposed )
88
- {
89
- throw new ObjectDisposedException ( nameof ( HttpResponseStreamWriter ) ) ;
90
- }
91
-
92
68
if ( value == null )
93
69
{
94
70
return ;
95
71
}
96
72
97
- Write ( value . AsSpan ( ) ) ;
73
+ WriteInternal ( value . AsSpan ( ) ) ;
98
74
}
99
75
100
76
public override void WriteLine ( ReadOnlySpan < char > value )
101
- {
102
- if ( _disposed )
103
- {
104
- throw new ObjectDisposedException ( nameof ( HttpResponseStreamWriter ) ) ;
105
- }
106
-
107
- Write ( value ) ;
108
- Write ( NewLine ) ;
109
- }
77
+ => WriteInternal ( value , addNewLine : true ) ;
110
78
111
79
public override Task WriteAsync ( char value )
112
80
{
113
- if ( _disposed )
114
- {
115
- return GetObjectDisposedTask ( ) ;
116
- }
117
-
118
81
_singleCharArray [ 0 ] = value ;
119
82
120
- return WriteAsync ( _singleCharArray , 0 , 1 ) ;
83
+ return WriteInternalAsync ( _singleCharArray . AsSpan ( 0 , 1 ) ) ;
121
84
}
122
85
123
86
public override Task WriteAsync ( char [ ] values , int index , int count )
124
- {
125
- if ( _disposed )
126
- {
127
- return GetObjectDisposedTask ( ) ;
128
- }
129
-
130
- if ( values == null || count == 0 )
131
- {
132
- return Task . CompletedTask ;
133
- }
134
-
135
- var value = new Span < char > ( values , index , count ) ;
136
- Write ( value ) ;
137
-
138
- return Task . CompletedTask ;
139
- }
87
+ => WriteInternalAsync ( values . AsSpan ( index , count ) ) ;
140
88
141
89
public override Task WriteAsync ( string ? value )
142
90
{
143
- if ( _disposed )
91
+ if ( value == null )
144
92
{
145
- return GetObjectDisposedTask ( ) ;
93
+ return Task . CompletedTask ;
146
94
}
147
95
148
- var length = _encoder . GetByteCount ( value , false ) ;
149
- var buffer = _writer . GetSpan ( length ) ;
150
- _encoder . GetBytes ( value , buffer , false ) ;
151
- _writer . Advance ( length ) ;
152
-
153
- return Task . CompletedTask ;
96
+ return WriteInternalAsync ( value . AsSpan ( ) ) ;
154
97
}
155
98
156
99
public override Task WriteAsync ( ReadOnlyMemory < char > value , CancellationToken cancellationToken = default )
100
+ => WriteInternalAsync ( value . Span , cancellationToken ) ;
101
+
102
+
103
+ public override Task WriteLineAsync ( ReadOnlyMemory < char > value , CancellationToken cancellationToken = default )
104
+ => WriteInternalAsync ( value . Span , cancellationToken , addNewLine : true ) ;
105
+
106
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
107
+ private Task WriteInternalAsync ( ReadOnlySpan < char > value , CancellationToken cancellationToken = default , bool addNewLine = false )
157
108
{
158
109
if ( _disposed )
159
110
{
@@ -165,37 +116,49 @@ public override Task WriteAsync(ReadOnlyMemory<char> value, CancellationToken ca
165
116
return Task . FromCanceled ( cancellationToken ) ;
166
117
}
167
118
168
- if ( value . IsEmpty )
119
+ if ( value . IsEmpty && ! addNewLine )
169
120
{
170
121
return Task . CompletedTask ;
171
122
}
172
123
173
- Write ( value . Span ) ;
124
+ WriteInternal ( value , addNewLine ) ;
174
125
175
- return Task . CompletedTask ;
126
+ return LazyFlushAsync ( cancellationToken ) ;
176
127
}
177
128
178
- public override Task WriteLineAsync ( ReadOnlyMemory < char > value , CancellationToken cancellationToken = default )
129
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
130
+ private Task LazyFlushAsync ( CancellationToken cancellationToken = default )
179
131
{
180
- if ( _disposed )
132
+ // The max size of a chunk is 4089.
133
+ if ( _uncommittedBytes >= 4089 )
181
134
{
182
- return GetObjectDisposedTask ( ) ;
135
+ _uncommittedBytes = 0 ;
136
+ return _writer . FlushAsync ( cancellationToken ) . AsTask ( ) ;
183
137
}
184
138
185
- if ( cancellationToken . IsCancellationRequested )
186
- {
187
- return Task . FromCanceled ( cancellationToken ) ;
188
- }
139
+ return Task . CompletedTask ;
140
+ }
189
141
190
- if ( value . IsEmpty && NewLine . Length == 0 )
142
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
143
+ private void WriteInternal ( ReadOnlySpan < char > value , bool addNewLine = false )
144
+ {
145
+ if ( _disposed )
191
146
{
192
- return Task . CompletedTask ;
147
+ throw new ObjectDisposedException ( nameof ( HttpResponseStreamWriter ) ) ;
193
148
}
194
149
195
- Write ( value . Span ) ;
196
- Write ( NewLine ) ;
150
+ WriteSpan ( value ) ;
151
+ if ( addNewLine ) WriteSpan ( NewLine ) ;
152
+ }
197
153
198
- return Task . CompletedTask ;
154
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
155
+ private void WriteSpan ( ReadOnlySpan < char > value )
156
+ {
157
+ var length = _encoder . GetByteCount ( value , false ) ;
158
+ var buffer = _writer . GetSpan ( length ) ;
159
+ _uncommittedBytes += length ;
160
+ _encoder . GetBytes ( value , buffer , false ) ;
161
+ _writer . Advance ( length ) ;
199
162
}
200
163
201
164
public override void Flush ( )
@@ -206,7 +169,7 @@ public override void Flush()
206
169
}
207
170
208
171
FlushEncoder ( ) ;
209
- // TOOD: flush
172
+ // TOOD: flush?
210
173
}
211
174
212
175
// Perf: FlushAsync is invoked to ensure any buffered content is asynchronously written to the underlying
@@ -228,7 +191,6 @@ public override async ValueTask DisposeAsync()
228
191
{
229
192
_disposed = true ;
230
193
await FlushInternalAsync ( ) ;
231
- _writer . Complete ( ) ;
232
194
}
233
195
234
196
await base . DisposeAsync ( ) ;
@@ -240,8 +202,7 @@ protected override void Dispose(bool disposing)
240
202
{
241
203
_disposed = true ;
242
204
FlushEncoder ( ) ;
243
- // TOOD: flush
244
- _writer . Complete ( ) ;
205
+ // Flush not needed
245
206
}
246
207
247
208
base . Dispose ( disposing ) ;
@@ -255,7 +216,6 @@ private ValueTask<FlushResult> FlushInternalAsync()
255
216
256
217
private void FlushEncoder ( )
257
218
{
258
- // flush encoder
259
219
var empty = new ReadOnlySpan < char > ( ) ;
260
220
var length = _encoder . GetByteCount ( empty , true ) ;
261
221
if ( length > 0 )
0 commit comments