Skip to content

Commit 29d9395

Browse files
committed
Fix possible nre while writing to RawCopyStream after break (#5213)
Fixes #5209 (cherry picked from commit d8c70e5) # Conflicts: # test/Npgsql.Tests/CopyTests.cs
1 parent f60a04e commit 29d9395

File tree

3 files changed

+44
-36
lines changed

3 files changed

+44
-36
lines changed

src/Npgsql/NpgsqlRawCopyStream.cs

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -143,25 +143,17 @@ public override void Write(ReadOnlySpan<byte> buffer)
143143
return;
144144
}
145145

146-
try
147-
{
148-
// Value is too big, flush.
149-
Flush();
150-
151-
if (buffer.Length <= _writeBuf.WriteSpaceLeft)
152-
{
153-
_writeBuf.WriteBytes(buffer);
154-
return;
155-
}
146+
// Value is too big, flush.
147+
Flush();
156148

157-
// Value is too big even after a flush - bypass the buffer and write directly.
158-
_writeBuf.DirectWrite(buffer);
159-
}
160-
catch (Exception e)
149+
if (buffer.Length <= _writeBuf.WriteSpaceLeft)
161150
{
162-
_connector.Break(e);
163-
throw;
151+
_writeBuf.WriteBytes(buffer);
152+
return;
164153
}
154+
155+
// Value is too big even after a flush - bypass the buffer and write directly.
156+
_writeBuf.DirectWrite(buffer);
165157
}
166158

167159
#if NETSTANDARD2_0
@@ -188,25 +180,17 @@ async ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> buffer, CancellationToke
188180
return;
189181
}
190182

191-
try
192-
{
193-
// Value is too big, flush.
194-
await FlushAsync(true, cancellationToken);
195-
196-
if (buffer.Length <= _writeBuf.WriteSpaceLeft)
197-
{
198-
_writeBuf.WriteBytes(buffer.Span);
199-
return;
200-
}
183+
// Value is too big, flush.
184+
await FlushAsync(true, cancellationToken);
201185

202-
// Value is too big even after a flush - bypass the buffer and write directly.
203-
await _writeBuf.DirectWrite(buffer, true, cancellationToken);
204-
}
205-
catch (Exception e)
186+
if (buffer.Length <= _writeBuf.WriteSpaceLeft)
206187
{
207-
_connector.Break(e);
208-
throw;
188+
_writeBuf.WriteBytes(buffer.Span);
189+
return;
209190
}
191+
192+
// Value is too big even after a flush - bypass the buffer and write directly.
193+
await _writeBuf.DirectWrite(buffer, true, cancellationToken);
210194
}
211195
}
212196

@@ -581,4 +565,4 @@ public ValueTask DisposeAsync()
581565
Dispose();
582566
return default;
583567
}
584-
}
568+
}

test/Npgsql.Tests/CopyTests.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Threading;
99
using System.Threading.Tasks;
1010
using Npgsql.Internal;
11+
using Npgsql.Tests.Support;
1112
using NpgsqlTypes;
1213
using NUnit.Framework;
1314
using static Npgsql.Tests.TestUtil;
@@ -1115,6 +1116,29 @@ public async Task Copy_is_not_supported_in_regular_command_execution()
11151116
Assert.That(() => conn.ExecuteNonQuery($@"COPY {table} (foo) FROM stdin"), Throws.Exception.TypeOf<NotSupportedException>());
11161117
}
11171118

1119+
[Test, IssueLink("https://github.com/npgsql/npgsql/issues/5209")]
1120+
[Platform(Exclude = "MacOsX", Reason = "Write might not throw an exception")]
1121+
public async Task RawBinaryCopy_write_nre([Values] bool async)
1122+
{
1123+
await using var postmasterMock = PgPostmasterMock.Start(ConnectionString);
1124+
await using var dataSource = NpgsqlDataSource.Create(postmasterMock.ConnectionString);
1125+
await using var conn = await dataSource.OpenConnectionAsync();
1126+
1127+
var server = await postmasterMock.WaitForServerConnection();
1128+
await server
1129+
.WriteCopyInResponse(isBinary: true)
1130+
.FlushAsync();
1131+
1132+
await using var stream = await conn.BeginRawBinaryCopyAsync("COPY SomeTable (field_text, field_int4) FROM STDIN");
1133+
server.Close();
1134+
var value = Encoding.UTF8.GetBytes(new string('a', conn.Settings.WriteBufferSize * 2));
1135+
if (async)
1136+
Assert.ThrowsAsync<NpgsqlException>(async () => await stream.WriteAsync(value));
1137+
else
1138+
Assert.Throws<NpgsqlException>(() => stream.Write(value));
1139+
Assert.That(conn.FullState, Is.EqualTo(ConnectionState.Broken));
1140+
}
1141+
11181142
#endregion
11191143

11201144
#region Utils

test/Npgsql.Tests/Support/PgServerMock.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,12 +328,12 @@ internal PgServerMock WriteBackendKeyData(int processId, int secret)
328328
internal PgServerMock WriteCancellationResponse()
329329
=> WriteErrorResponse(PostgresErrorCodes.QueryCanceled, "Cancellation", "Query cancelled");
330330

331-
internal PgServerMock WriteCopyInResponse()
331+
internal PgServerMock WriteCopyInResponse(bool isBinary = false)
332332
{
333333
CheckDisposed();
334334
_writeBuffer.WriteByte((byte)BackendMessageCode.CopyInResponse);
335335
_writeBuffer.WriteInt32(5);
336-
_writeBuffer.WriteByte(0);
336+
_writeBuffer.WriteByte(isBinary ? (byte)1 : (byte)0);
337337
_writeBuffer.WriteInt16(1);
338338
_writeBuffer.WriteInt16(0);
339339
return this;
@@ -381,4 +381,4 @@ public void Dispose()
381381

382382
_disposed = true;
383383
}
384-
}
384+
}

0 commit comments

Comments
 (0)