Skip to content
This repository was archived by the owner on Jun 22, 2025. It is now read-only.

Commit 53c6dff

Browse files
authored
Fixed memory leak highlighted in #632 (#633)
1 parent 6f9280a commit 53c6dff

File tree

1 file changed

+23
-7
lines changed

1 file changed

+23
-7
lines changed

ClickHouse.Client/Copy/ClickHouseBulkCopy.cs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,18 @@ namespace ClickHouse.Client.Copy;
1616

1717
public class ClickHouseBulkCopy : IDisposable
1818
{
19-
private static readonly RecyclableMemoryStreamManager MemoryStreamManager = new();
19+
private static readonly RecyclableMemoryStreamManager CommonMemoryStreamManager = new(new RecyclableMemoryStreamManager.Options
20+
{
21+
MaximumLargePoolFreeBytes = 512 * 1024 * 1024,
22+
MaximumSmallPoolFreeBytes = 128 * 1024 * 1024,
23+
BlockSize = 256 * 1024,
24+
});
25+
2026
private readonly ClickHouseConnection connection;
2127
private readonly BatchSerializer batchSerializer;
2228
private readonly RowBinaryFormat rowBinaryFormat;
2329
private readonly bool ownsConnection;
30+
private readonly RecyclableMemoryStreamManager memoryStreamManager;
2431
private long rowsWritten;
2532
private (string[] names, ClickHouseType[] types) columnNamesAndTypes;
2633

@@ -85,12 +92,13 @@ public long RowsWritten
8592
}
8693
}
8794

88-
private async Task<(string[] names, ClickHouseType[] types)> LoadNamesAndTypesAsync(string destinationTableName, IReadOnlyCollection<string> columns = null)
95+
/// <summary>
96+
/// Gets RecyclableMemoryStreamManager used to create recyclable streams.
97+
/// </summary>
98+
public RecyclableMemoryStreamManager MemoryStreamManager
8999
{
90-
using var reader = (ClickHouseDataReader)await connection.ExecuteReaderAsync($"SELECT {GetColumnsExpression(columns)} FROM {DestinationTableName} WHERE 1=0").ConfigureAwait(false);
91-
var types = reader.GetClickHouseColumnTypes();
92-
var names = reader.GetColumnNames().Select(c => c.EncloseColumnName()).ToArray();
93-
return (names, types);
100+
get { return memoryStreamManager ?? CommonMemoryStreamManager; }
101+
init { memoryStreamManager = value; }
94102
}
95103

96104
/// <summary>
@@ -172,11 +180,19 @@ public async Task WriteToServerAsync(IEnumerable<object[]> rows, CancellationTok
172180
await Task.WhenAll(tasks).ConfigureAwait(false);
173181
}
174182

183+
private async Task<(string[] names, ClickHouseType[] types)> LoadNamesAndTypesAsync(string destinationTableName, IReadOnlyCollection<string> columns = null)
184+
{
185+
using var reader = (ClickHouseDataReader)await connection.ExecuteReaderAsync($"SELECT {GetColumnsExpression(columns)} FROM {DestinationTableName} WHERE 1=0").ConfigureAwait(false);
186+
var types = reader.GetClickHouseColumnTypes();
187+
var names = reader.GetColumnNames().Select(c => c.EncloseColumnName()).ToArray();
188+
return (names, types);
189+
}
190+
175191
private async Task SendBatchAsync(Batch batch, CancellationToken token)
176192
{
177193
using (batch) // Dispose object regardless whether sending succeeds
178194
{
179-
using var stream = MemoryStreamManager.GetStream(nameof(SendBatchAsync));
195+
using var stream = MemoryStreamManager.GetStream(nameof(SendBatchAsync), 128 * 1024);
180196
// Async serialization
181197
await Task.Run(() => batchSerializer.Serialize(batch, stream), token).ConfigureAwait(false);
182198
// Seek to beginning as after writing it's at end

0 commit comments

Comments
 (0)