@@ -23,14 +23,9 @@ namespace System.IO
23
23
* of the UnmanagedMemoryStream.
24
24
* 3) You clean up the memory when appropriate. The UnmanagedMemoryStream
25
25
* currently will do NOTHING to free this memory.
26
- * 4) All calls to Write and WriteByte may not be threadsafe currently.
27
- *
28
- * It may become necessary to add in some sort of
29
- * DeallocationMode enum, specifying whether we unmap a section of memory,
30
- * call free, run a user-provided delegate to free the memory, etc.
31
- * We'll suggest user write a subclass of UnmanagedMemoryStream that uses
32
- * a SafeHandle subclass to hold onto the memory.
33
- *
26
+ * 4) This type is not thread safe. However, the implementation should prevent buffer
27
+ * overruns or returning uninitialized memory when Reads and Writes are called
28
+ * concurrently in thread unsafe manner.
34
29
*/
35
30
36
31
/// <summary>
@@ -40,10 +35,10 @@ public class UnmanagedMemoryStream : Stream
40
35
{
41
36
private SafeBuffer ? _buffer ;
42
37
private unsafe byte * _mem ;
43
- private long _length ;
44
- private long _capacity ;
45
- private long _position ;
46
- private long _offset ;
38
+ private nuint _capacity ;
39
+ private nuint _offset ;
40
+ private nuint _length ; // nuint to guarantee atomic access on 32-bit platforms
41
+ private long _position ; // long to allow seeking to any location beyond the length of the stream.
47
42
private FileAccess _access ;
48
43
private bool _isOpen ;
49
44
private CachedCompletedInt32Task _lastReadTask ; // The last successful task returned from ReadAsync
@@ -123,10 +118,10 @@ protected void Initialize(SafeBuffer buffer, long offset, long length, FileAcces
123
118
}
124
119
}
125
120
126
- _offset = offset ;
121
+ _offset = ( nuint ) offset ;
127
122
_buffer = buffer ;
128
- _length = length ;
129
- _capacity = length ;
123
+ _length = ( nuint ) length ;
124
+ _capacity = ( nuint ) length ;
130
125
_access = access ;
131
126
_isOpen = true ;
132
127
}
@@ -171,8 +166,8 @@ protected unsafe void Initialize(byte* pointer, long length, long capacity, File
171
166
172
167
_mem = pointer ;
173
168
_offset = 0 ;
174
- _length = length ;
175
- _capacity = capacity ;
169
+ _length = ( nuint ) length ;
170
+ _capacity = ( nuint ) capacity ;
176
171
_access = access ;
177
172
_isOpen = true ;
178
173
}
@@ -259,7 +254,7 @@ public override long Length
259
254
get
260
255
{
261
256
EnsureNotClosed ( ) ;
262
- return Interlocked . Read ( ref _length ) ;
257
+ return ( long ) _length ;
263
258
}
264
259
}
265
260
@@ -271,7 +266,7 @@ public long Capacity
271
266
get
272
267
{
273
268
EnsureNotClosed ( ) ;
274
- return _capacity ;
269
+ return ( long ) _capacity ;
275
270
}
276
271
}
277
272
@@ -283,14 +278,14 @@ public override long Position
283
278
get
284
279
{
285
280
if ( ! CanSeek ) ThrowHelper . ThrowObjectDisposedException_StreamClosed ( null ) ;
286
- return Interlocked . Read ( ref _position ) ;
281
+ return _position ;
287
282
}
288
283
set
289
284
{
290
285
ArgumentOutOfRangeException . ThrowIfNegative ( value ) ;
291
286
if ( ! CanSeek ) ThrowHelper . ThrowObjectDisposedException_StreamClosed ( null ) ;
292
287
293
- Interlocked . Exchange ( ref _position , value ) ;
288
+ _position = value ;
294
289
}
295
290
}
296
291
@@ -308,11 +303,10 @@ public unsafe byte* PositionPointer
308
303
EnsureNotClosed ( ) ;
309
304
310
305
// Use a temp to avoid a race
311
- long pos = Interlocked . Read ( ref _position ) ;
312
- if ( pos > _capacity )
306
+ long pos = _position ;
307
+ if ( pos > ( long ) _capacity )
313
308
throw new IndexOutOfRangeException ( SR . IndexOutOfRange_UMSPosition ) ;
314
- byte * ptr = _mem + pos ;
315
- return ptr ;
309
+ return _mem + pos ;
316
310
}
317
311
set
318
312
{
@@ -327,7 +321,7 @@ public unsafe byte* PositionPointer
327
321
if ( newPosition < 0 )
328
322
throw new ArgumentOutOfRangeException ( nameof ( value ) , SR . ArgumentOutOfRange_UnmanagedMemStreamLength ) ;
329
323
330
- Interlocked . Exchange ( ref _position , newPosition ) ;
324
+ _position = newPosition ;
331
325
}
332
326
}
333
327
@@ -367,8 +361,8 @@ internal int ReadCore(Span<byte> buffer)
367
361
368
362
// Use a local variable to avoid a race where another thread
369
363
// changes our position after we decide we can read some bytes.
370
- long pos = Interlocked . Read ( ref _position ) ;
371
- long len = Interlocked . Read ( ref _length ) ;
364
+ long pos = _position ;
365
+ long len = ( long ) Volatile . Read ( ref _length ) ;
372
366
long n = Math . Min ( len - pos , buffer . Length ) ;
373
367
if ( n <= 0 )
374
368
{
@@ -407,7 +401,7 @@ internal int ReadCore(Span<byte> buffer)
407
401
}
408
402
}
409
403
410
- Interlocked . Exchange ( ref _position , pos + n ) ;
404
+ _position = pos + n ;
411
405
return nInt;
412
406
}
413
407
@@ -484,11 +478,11 @@ public override int ReadByte()
484
478
EnsureNotClosed ( ) ;
485
479
EnsureReadable ( ) ;
486
480
487
- long pos = Interlocked . Read ( ref _position ) ; // Use a local to avoid a race condition
488
- long len = Interlocked . Read ( ref _length ) ;
481
+ long pos = _position ; // Use a local to avoid a race condition
482
+ long len = ( long ) Volatile . Read ( ref _length ) ;
489
483
if ( pos >= len )
490
484
return - 1 ;
491
- Interlocked . Exchange ( ref _position , pos + 1 ) ;
485
+ _position = pos + 1 ;
492
486
int result ;
493
487
if ( _buffer != null )
494
488
{
@@ -529,35 +523,33 @@ public override long Seek(long offset, SeekOrigin loc)
529
523
{
530
524
EnsureNotClosed ( ) ;
531
525
526
+ long newPosition ;
532
527
switch ( loc )
533
528
{
534
529
case SeekOrigin . Begin :
535
- if ( offset < 0 )
530
+ newPosition = offset ;
531
+ if ( newPosition < 0 )
536
532
throw new IOException ( SR . IO_SeekBeforeBegin ) ;
537
- Interlocked . Exchange ( ref _position , offset ) ;
538
533
break ;
539
534
540
535
case SeekOrigin . Current :
541
- long pos = Interlocked . Read ( ref _position ) ;
542
- if ( offset + pos < 0 )
536
+ newPosition = _position + offset ;
537
+ if ( newPosition < 0 )
543
538
throw new IOException ( SR . IO_SeekBeforeBegin ) ;
544
- Interlocked . Exchange ( ref _position , offset + pos ) ;
545
539
break ;
546
540
547
541
case SeekOrigin . End :
548
- long len = Interlocked . Read ( ref _length ) ;
549
- if ( len + offset < 0 )
542
+ newPosition = ( long ) _length + offset ;
543
+ if ( newPosition < 0 )
550
544
throw new IOException ( SR . IO_SeekBeforeBegin ) ;
551
- Interlocked . Exchange ( ref _position , len + offset ) ;
552
545
break ;
553
546
554
547
default :
555
548
throw new ArgumentException ( SR . Argument_InvalidSeekOrigin ) ;
556
549
}
557
550
558
- long finalPos = Interlocked . Read ( ref _position ) ;
559
- Debug . Assert ( finalPos >= 0 , "_position >= 0" ) ;
560
- return finalPos ;
551
+ _position = newPosition ;
552
+ return newPosition ;
561
553
}
562
554
563
555
/// <summary>
@@ -573,22 +565,22 @@ public override void SetLength(long value)
573
565
EnsureNotClosed ( ) ;
574
566
EnsureWriteable ( ) ;
575
567
576
- if ( value > _capacity )
568
+ if ( value > ( long ) _capacity )
577
569
throw new IOException ( SR . IO_FixedCapacity ) ;
578
570
579
- long pos = Interlocked . Read ( ref _position ) ;
580
- long len = Interlocked . Read ( ref _length ) ;
571
+ long len = ( long ) _length ;
581
572
if ( value > len )
582
573
{
583
574
unsafe
584
575
{
585
576
NativeMemory . Clear ( _mem + len , ( nuint ) ( value - len ) ) ;
586
577
}
587
578
}
588
- Interlocked . Exchange ( ref _length , value ) ;
589
- if ( pos > value )
579
+ Volatile . Write ( ref _length , ( nuint ) value ) ; // volatile to prevent reading of uninitialized memory
580
+
581
+ if ( _position > value )
590
582
{
591
- Interlocked . Exchange ( ref _position , value ) ;
583
+ _position = value ;
592
584
}
593
585
}
594
586
@@ -625,16 +617,16 @@ internal unsafe void WriteCore(ReadOnlySpan<byte> buffer)
625
617
EnsureNotClosed ( ) ;
626
618
EnsureWriteable ( ) ;
627
619
628
- long pos = Interlocked . Read ( ref _position ) ; // Use a local to avoid a race condition
629
- long len = Interlocked . Read ( ref _length ) ;
620
+ long pos = _position ; // Use a local to avoid a race condition
621
+ long len = ( long ) _length ;
630
622
long n = pos + buffer . Length ;
631
623
// Check for overflow
632
624
if ( n < 0 )
633
625
{
634
626
throw new IOException ( SR . IO_StreamTooLong ) ;
635
627
}
636
628
637
- if ( n > _capacity )
629
+ if ( n > ( long ) _capacity )
638
630
{
639
631
throw new NotSupportedException ( SR . IO_FixedCapacity ) ;
640
632
}
@@ -648,16 +640,16 @@ internal unsafe void WriteCore(ReadOnlySpan<byte> buffer)
648
640
NativeMemory . Clear ( _mem + len , ( nuint ) ( pos - len ) ) ;
649
641
}
650
642
651
- // set length after zeroing memory to avoid race condition of accessing unzeroed memory
643
+ // set length after zeroing memory to avoid race condition of accessing uninitialized memory
652
644
if ( n > len )
653
645
{
654
- Interlocked . Exchange ( ref _length , n ) ;
646
+ Volatile . Write ( ref _length , ( nuint ) n ) ; // volatile to prevent reading of uninitialized memory
655
647
}
656
648
}
657
649
658
650
if ( _buffer != null )
659
651
{
660
- long bytesLeft = _capacity - pos ;
652
+ long bytesLeft = ( long ) _capacity - pos ;
661
653
if ( bytesLeft < buffer . Length )
662
654
{
663
655
throw new ArgumentException ( SR . Arg_BufferTooSmall ) ;
@@ -682,8 +674,7 @@ internal unsafe void WriteCore(ReadOnlySpan<byte> buffer)
682
674
Buffer . Memmove ( ref * ( _mem + pos ) , ref MemoryMarshal . GetReference ( buffer ) , ( nuint ) buffer . Length ) ;
683
675
}
684
676
685
- Interlocked . Exchange ( ref _position , n ) ;
686
- return ;
677
+ _position = n ;
687
678
}
688
679
689
680
/// <summary>
@@ -754,16 +745,16 @@ public override void WriteByte(byte value)
754
745
EnsureNotClosed ( ) ;
755
746
EnsureWriteable ( ) ;
756
747
757
- long pos = Interlocked . Read ( ref _position ) ; // Use a local to avoid a race condition
758
- long len = Interlocked . Read ( ref _length ) ;
748
+ long pos = _position ; // Use a local to avoid a race condition
749
+ long len = ( long ) _length ;
759
750
long n = pos + 1 ;
760
751
if ( pos >= len )
761
752
{
762
753
// Check for overflow
763
754
if ( n < 0 )
764
755
throw new IOException ( SR . IO_StreamTooLong ) ;
765
756
766
- if ( n > _capacity )
757
+ if ( n > ( long ) _capacity )
767
758
throw new NotSupportedException ( SR . IO_FixedCapacity ) ;
768
759
769
760
// Check to see whether we are now expanding the stream and must
@@ -779,8 +770,7 @@ public override void WriteByte(byte value)
779
770
}
780
771
}
781
772
782
- // set length after zeroing memory to avoid race condition of accessing unzeroed memory
783
- Interlocked . Exchange ( ref _length , n ) ;
773
+ Volatile . Write ( ref _length , ( nuint ) n ) ; // volatile to prevent reading of uninitialized memory
784
774
}
785
775
}
786
776
@@ -810,7 +800,7 @@ public override void WriteByte(byte value)
810
800
_mem [ pos ] = value ;
811
801
}
812
802
}
813
- Interlocked . Exchange ( ref _position , n ) ;
803
+ _position = n ;
814
804
}
815
805
}
816
806
}
0 commit comments