Skip to content

Commit 0685c54

Browse files
committed
CSHARP-2373: GridFSForwardOnlyUploadStream not closing properly on .NET Standard 2+ when cast to Stream before calling Close.
1 parent e21e59c commit 0685c54

File tree

3 files changed

+167
-66
lines changed

3 files changed

+167
-66
lines changed

src/MongoDB.Driver.GridFS/GridFSDownloadStreamBase.cs

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ internal abstract class GridFSDownloadStreamBase<TFileId> : GridFSDownloadStream
2626
// private fields
2727
private readonly IReadBinding _binding;
2828
private readonly IGridFSBucket<TFileId> _bucket;
29+
private bool _closed;
2930
private bool _disposed;
3031
private readonly GridFSFileInfo<TFileId> _fileInfo;
3132

@@ -73,20 +74,28 @@ protected IGridFSBucket<TFileId> Bucket
7374
}
7475

7576
// public methods
76-
public override void Close()
77-
{
78-
Close(CancellationToken.None);
79-
}
80-
8177
public override void Close(CancellationToken cancellationToken)
8278
{
83-
base.Close();
79+
try
80+
{
81+
CloseIfNotAlreadyClosed(cancellationToken);
82+
}
83+
finally
84+
{
85+
Dispose();
86+
}
8487
}
8588

86-
public override Task CloseAsync(CancellationToken cancellationToken = default(CancellationToken))
89+
public override async Task CloseAsync(CancellationToken cancellationToken = default(CancellationToken))
8790
{
88-
base.Close();
89-
return Task.FromResult(true);
91+
try
92+
{
93+
await CloseIfNotAlreadyClosedAsync(cancellationToken).ConfigureAwait(false);
94+
}
95+
finally
96+
{
97+
Dispose();
98+
}
9099
}
91100

92101
public override void Flush()
@@ -115,8 +124,37 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati
115124
}
116125

117126
// protected methods
127+
protected void CloseIfNotAlreadyClosedFromDispose(bool disposing)
128+
{
129+
if (disposing)
130+
{
131+
try
132+
{
133+
CloseIfNotAlreadyClosed(CancellationToken.None);
134+
}
135+
catch
136+
{
137+
// ignore exceptions when calling CloseIfNotAlreadyClosed from Dispose
138+
}
139+
}
140+
}
141+
142+
protected virtual void CloseImplementation(CancellationToken cancellationToken)
143+
{
144+
// override in subclass if there is anything to do
145+
}
146+
147+
protected virtual Task CloseImplementationAsync(CancellationToken cancellationToken)
148+
{
149+
// override in subclass if there is anything to do
150+
CloseImplementation(cancellationToken);
151+
return Task.FromResult(true);
152+
}
153+
118154
protected override void Dispose(bool disposing)
119155
{
156+
CloseIfNotAlreadyClosedFromDispose(disposing);
157+
120158
if (!_disposed)
121159
{
122160
if (disposing)
@@ -137,5 +175,36 @@ protected virtual void ThrowIfDisposed()
137175
throw new ObjectDisposedException(GetType().Name);
138176
}
139177
}
178+
179+
// private methods
180+
private void CloseIfNotAlreadyClosed(CancellationToken cancellationToken)
181+
{
182+
if (!_closed)
183+
{
184+
try
185+
{
186+
CloseImplementation(cancellationToken);
187+
}
188+
finally
189+
{
190+
_closed = true;
191+
}
192+
}
193+
}
194+
195+
private async Task CloseIfNotAlreadyClosedAsync(CancellationToken cancellationToken)
196+
{
197+
if (!_closed)
198+
{
199+
try
200+
{
201+
await CloseImplementationAsync(cancellationToken).ConfigureAwait(false);
202+
}
203+
finally
204+
{
205+
_closed = true;
206+
}
207+
}
208+
}
140209
}
141210
}

src/MongoDB.Driver.GridFS/GridFSForwardOnlyDownloadStream.cs

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ internal class GridFSForwardOnlyDownloadStream<TFileId> : GridFSDownloadStreamBa
3636
private List<BsonDocument> _batch;
3737
private long _batchPosition;
3838
private readonly bool _checkMD5;
39-
private bool _closed;
4039
private IAsyncCursor<BsonDocument> _cursor;
4140
private bool _disposed;
4241
private readonly BsonValue _idAsBsonValue;
@@ -92,18 +91,6 @@ public override long Position
9291
}
9392

9493
// methods
95-
public override void Close(CancellationToken cancellationToken)
96-
{
97-
CloseHelper();
98-
base.Close(cancellationToken);
99-
}
100-
101-
public override Task CloseAsync(CancellationToken cancellationToken = default(CancellationToken))
102-
{
103-
CloseHelper();
104-
return base.CloseAsync(cancellationToken);
105-
}
106-
10794
public override int Read(byte[] buffer, int offset, int count)
10895
{
10996
Ensure.IsNotNull(buffer, nameof(buffer));
@@ -158,8 +145,24 @@ public override long Seek(long offset, SeekOrigin origin)
158145
}
159146

160147
// protected methods
148+
protected override void CloseImplementation(CancellationToken cancellationToken)
149+
{
150+
if (_checkMD5 && _position == FileInfo.Length)
151+
{
152+
var md5 = BsonUtils.ToHexString(_md5.GetHashAndReset());
153+
if (!md5.Equals(FileInfo.MD5, StringComparison.OrdinalIgnoreCase))
154+
{
155+
#pragma warning disable 618
156+
throw new GridFSMD5Exception(_idAsBsonValue);
157+
#pragma warning restore
158+
}
159+
}
160+
}
161+
161162
protected override void Dispose(bool disposing)
162163
{
164+
CloseIfNotAlreadyClosedFromDispose(disposing);
165+
163166
if (!_disposed)
164167
{
165168
if (disposing)
@@ -190,25 +193,6 @@ protected override void ThrowIfDisposed()
190193
}
191194

192195
// private methods
193-
private void CloseHelper()
194-
{
195-
if (!_closed)
196-
{
197-
_closed = true;
198-
199-
if (_checkMD5 && _position == FileInfo.Length)
200-
{
201-
var md5 = BsonUtils.ToHexString(_md5.GetHashAndReset());
202-
if (!md5.Equals(FileInfo.MD5, StringComparison.OrdinalIgnoreCase))
203-
{
204-
#pragma warning disable 618
205-
throw new GridFSMD5Exception(_idAsBsonValue);
206-
#pragma warning restore
207-
}
208-
}
209-
}
210-
}
211-
212196
private FindOperation<BsonDocument> CreateFirstBatchOperation()
213197
{
214198
var chunksCollectionNamespace = Bucket.GetChunksCollectionNamespace();

src/MongoDB.Driver.GridFS/GridFSForwardOnlyUploadStream.cs

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -151,45 +151,28 @@ public override long Position
151151
await operation.ExecuteAsync(_binding, cancellationToken).ConfigureAwait(false);
152152
}
153153

154-
public override void Close()
155-
{
156-
Close(CancellationToken.None);
157-
}
158-
159154
public override void Close(CancellationToken cancellationToken)
160155
{
161-
if (_closed)
156+
try
162157
{
163-
return;
158+
CloseIfNotAlreadyClosed(cancellationToken);
164159
}
165-
ThrowIfDisposed();
166-
_closed = true;
167-
168-
if (!_aborted)
160+
finally
169161
{
170-
WriteFinalBatch(cancellationToken);
171-
WriteFilesCollectionDocument(cancellationToken);
162+
Dispose();
172163
}
173-
174-
base.Close();
175164
}
176165

177166
public override async Task CloseAsync(CancellationToken cancellationToken = default(CancellationToken))
178167
{
179-
if (_closed)
168+
try
180169
{
181-
return;
170+
await CloseIfNotAlreadyClosedAsync(cancellationToken).ConfigureAwait(false);
182171
}
183-
ThrowIfDisposed();
184-
_closed = true;
185-
186-
if (!_aborted)
172+
finally
187173
{
188-
await WriteFinalBatchAsync(cancellationToken).ConfigureAwait(false);
189-
await WriteFilesCollectionDocumentAsync(cancellationToken).ConfigureAwait(false);
174+
Dispose();
190175
}
191-
192-
base.Close();
193176
}
194177

195178
public override void Flush()
@@ -252,6 +235,69 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc
252235
}
253236

254237
// private methods
238+
private void CloseIfNotAlreadyClosed(CancellationToken cancellationToken)
239+
{
240+
if (!_closed)
241+
{
242+
try
243+
{
244+
CloseImplementation(cancellationToken);
245+
}
246+
finally
247+
{
248+
_closed = true;
249+
}
250+
}
251+
}
252+
253+
private async Task CloseIfNotAlreadyClosedAsync(CancellationToken cancellationToken)
254+
{
255+
if (!_closed)
256+
{
257+
try
258+
{
259+
await CloseImplementationAsync(cancellationToken).ConfigureAwait(false);
260+
}
261+
finally
262+
{
263+
_closed = true;
264+
}
265+
}
266+
}
267+
268+
private void CloseIfNotAlreadyClosedFromDispose(bool disposing)
269+
{
270+
if (disposing)
271+
{
272+
try
273+
{
274+
CloseIfNotAlreadyClosed(CancellationToken.None);
275+
}
276+
catch
277+
{
278+
// ignore any exceptions from CloseIfNotAlreadyClosed when called from Dispose
279+
}
280+
}
281+
}
282+
283+
private void CloseImplementation(CancellationToken cancellationToken)
284+
{
285+
if (!_aborted)
286+
{
287+
WriteFinalBatch(cancellationToken);
288+
WriteFilesCollectionDocument(cancellationToken);
289+
}
290+
}
291+
292+
private async Task CloseImplementationAsync(CancellationToken cancellationToken = default(CancellationToken))
293+
{
294+
if (!_aborted)
295+
{
296+
await WriteFinalBatchAsync(cancellationToken).ConfigureAwait(false);
297+
await WriteFilesCollectionDocumentAsync(cancellationToken).ConfigureAwait(false);
298+
}
299+
}
300+
255301
private BulkMixedWriteOperation CreateAbortOperation()
256302
{
257303
var chunksCollectionNamespace = _bucket.GetChunksCollectionNamespace();
@@ -308,6 +354,8 @@ private IEnumerable<BsonDocument> CreateWriteBatchChunkDocuments()
308354

309355
protected override void Dispose(bool disposing)
310356
{
357+
CloseIfNotAlreadyClosedFromDispose(disposing);
358+
311359
if (!_disposed)
312360
{
313361
_disposed = true;

0 commit comments

Comments
 (0)