@@ -25,58 +25,67 @@ private void StartRaisingEvents()
2525 }
2626
2727 // If we're already running, don't do anything.
28- if ( ! IsHandleInvalid ( _directoryHandle ) )
28+ if ( ! IsHandleClosed ( _directoryHandle ) )
2929 return ;
3030
3131 // Create handle to directory being monitored
32- _directoryHandle = Interop . Kernel32 . CreateFile (
32+ SafeFileHandle directoryHandle = Interop . Kernel32 . CreateFile (
3333 lpFileName : _directory ,
3434 dwDesiredAccess : Interop . Kernel32 . FileOperations . FILE_LIST_DIRECTORY ,
3535 dwShareMode : FileShare . Read | FileShare . Delete | FileShare . Write ,
3636 dwCreationDisposition : FileMode . Open ,
3737 dwFlagsAndAttributes : Interop . Kernel32 . FileOperations . FILE_FLAG_BACKUP_SEMANTICS | Interop . Kernel32 . FileOperations . FILE_FLAG_OVERLAPPED ) ;
3838
39- if ( IsHandleInvalid ( _directoryHandle ) )
39+ if ( directoryHandle . IsInvalid )
4040 {
41- _directoryHandle = null ;
4241 throw new FileNotFoundException ( SR . Format ( SR . FSW_IOError , _directory ) ) ;
4342 }
4443
4544 // Create the state associated with the operation of monitoring the direction
46- AsyncReadState state ;
45+ AsyncReadState state = new (
46+ Interlocked . Increment ( ref _currentSession ) , // ignore all events that were initiated before this
47+ this ,
48+ directoryHandle ) ;
4749 try
4850 {
49- // Start ignoring all events that were initiated before this, and
50- // allocate the buffer to be pinned and used for the duration of the operation
51- int session = Interlocked . Increment ( ref _currentSession ) ;
52- byte [ ] buffer = AllocateBuffer ( ) ;
51+ unsafe
52+ {
53+ uint bufferSize = _internalBufferSize ;
54+ state . Buffer = NativeMemory . Alloc ( bufferSize ) ;
55+ state . BufferByteLength = bufferSize ;
56+ }
57+
58+ state . ThreadPoolBinding = ThreadPoolBoundHandle . BindHandle ( directoryHandle ) ;
5359
5460 // Store all state, including a preallocated overlapped, into the state object that'll be
5561 // passed from iteration to iteration during the lifetime of the operation. The buffer will be pinned
5662 // from now until the end of the operation.
57- state = new AsyncReadState ( session , buffer , _directoryHandle , ThreadPoolBoundHandle . BindHandle ( _directoryHandle ) , this ) ;
5863 unsafe
5964 {
6065 state . PreAllocatedOverlapped = new PreAllocatedOverlapped ( ( errorCode , numBytes , overlappedPointer ) =>
6166 {
6267 AsyncReadState state = ( AsyncReadState ) ThreadPoolBoundHandle . GetNativeOverlappedState ( overlappedPointer ) ! ;
63- state . ThreadPoolBinding . FreeNativeOverlapped ( overlappedPointer ) ;
68+ state . ThreadPoolBinding ! . FreeNativeOverlapped ( overlappedPointer ) ;
69+
6470 if ( state . WeakWatcher . TryGetTarget ( out FileSystemWatcher ? watcher ) )
6571 {
6672 watcher . ReadDirectoryChangesCallback ( errorCode , numBytes , state ) ;
6773 }
68- } , state , buffer ) ;
74+ else
75+ {
76+ state . Dispose ( ) ;
77+ }
78+ } , state , null ) ;
6979 }
7080 }
7181 catch
7282 {
73- // Make sure we don't leave a valid directory handle set if we're not running
74- _directoryHandle . Dispose ( ) ;
75- _directoryHandle = null ;
83+ state . Dispose ( ) ;
7684 throw ;
7785 }
7886
7987 // Start monitoring
88+ _directoryHandle = directoryHandle ;
8089 _enabled = true ;
8190 Monitor ( state ) ;
8291 }
@@ -90,7 +99,7 @@ private void StopRaisingEvents()
9099 return ;
91100
92101 // If we're not running, do nothing.
93- if ( IsHandleInvalid ( _directoryHandle ) )
102+ if ( IsHandleClosed ( _directoryHandle ) )
94103 return ;
95104
96105 // Start ignoring all events occurring after this.
@@ -118,7 +127,7 @@ private void FinalizeDispose()
118127 // We must explicitly dispose the handle to ensure it gets closed before this object is finalized.
119128 // Otherwise, it is possible that the GC will decide to finalize the handle after this,
120129 // leaving a window of time where our callback could be invoked on a non-existent object.
121- if ( ! IsHandleInvalid ( _directoryHandle ) )
130+ if ( ! IsHandleClosed ( _directoryHandle ) )
122131 _directoryHandle . Dispose ( ) ;
123132 }
124133
@@ -128,11 +137,23 @@ private void FinalizeDispose()
128137 // Unmanaged handle to monitored directory
129138 private SafeFileHandle ? _directoryHandle ;
130139
131- private static bool IsHandleInvalid ( [ NotNullWhen ( false ) ] SafeFileHandle ? handle )
140+ /// <summary>Allocates a buffer of the requested internal buffer size.</summary>
141+ /// <returns>The allocated buffer.</returns>
142+ private unsafe byte * AllocateBuffer ( )
132143 {
133- return handle == null || handle . IsInvalid || handle . IsClosed ;
144+ try
145+ {
146+ return ( byte * ) NativeMemory . Alloc ( _internalBufferSize ) ;
147+ }
148+ catch ( OutOfMemoryException )
149+ {
150+ throw new OutOfMemoryException ( SR . Format ( SR . BufferSizeTooLarge , _internalBufferSize ) ) ;
151+ }
134152 }
135153
154+ private static bool IsHandleClosed ( [ NotNullWhen ( false ) ] SafeFileHandle ? handle ) =>
155+ handle is null || handle . IsClosed ;
156+
136157 /// <summary>
137158 /// Initiates the next asynchronous read operation if monitoring is still desired.
138159 /// If the directory handle has been closed due to an error or due to event monitoring
@@ -141,6 +162,7 @@ private static bool IsHandleInvalid([NotNullWhen(false)] SafeFileHandle? handle)
141162 private unsafe void Monitor ( AsyncReadState state )
142163 {
143164 Debug . Assert ( state . PreAllocatedOverlapped != null ) ;
165+ Debug . Assert ( state . ThreadPoolBinding is not null && state . Buffer is not null , "Members should have been set at construction" ) ;
144166
145167 // This method should only ever access the directory handle via the state object passed in, and not access it
146168 // via _directoryHandle. While this function is executing asynchronously, another thread could set
@@ -150,35 +172,35 @@ private unsafe void Monitor(AsyncReadState state)
150172
151173 NativeOverlapped * overlappedPointer = null ;
152174 bool continueExecuting = false ;
175+ int win32Error = 0 ;
153176 try
154177 {
155178 // If shutdown has been requested, exit. The finally block will handle
156179 // cleaning up the entire operation, as continueExecuting will remain false.
157- if ( ! _enabled || IsHandleInvalid ( state . DirectoryHandle ) )
180+ if ( ! _enabled || IsHandleClosed ( state . DirectoryHandle ) )
158181 return ;
159182
160183 // Get the overlapped pointer to use for this iteration.
161184 overlappedPointer = state . ThreadPoolBinding . AllocateNativeOverlapped ( state . PreAllocatedOverlapped ) ;
162185 continueExecuting = Interop . Kernel32 . ReadDirectoryChangesW (
163186 state . DirectoryHandle ,
164- state . Buffer , // the buffer is kept pinned for the duration of the sync and async operation by the PreAllocatedOverlapped
165- ( uint ) state . Buffer . Length ,
187+ state . Buffer ,
188+ ( uint ) state . BufferByteLength ,
166189 _includeSubdirectories ,
167190 ( uint ) _notifyFilters ,
168191 null ,
169192 overlappedPointer ,
170193 null ) ;
194+ if ( ! continueExecuting )
195+ {
196+ win32Error = Marshal . GetLastWin32Error ( ) ;
197+ }
171198 }
172199 catch ( ObjectDisposedException )
173200 {
174201 // Ignore. Disposing of the handle is the mechanism by which the FSW communicates
175202 // to the asynchronous operation to stop processing.
176203 }
177- catch ( ArgumentNullException )
178- {
179- //Ignore. The disposed handle could also manifest as an ArgumentNullException.
180- Debug . Assert ( IsHandleInvalid ( state . DirectoryHandle ) , "ArgumentNullException from something other than SafeHandle?" ) ;
181- }
182204 finally
183205 {
184206 // At this point the operation has either been initiated and we'll let the callback
@@ -192,15 +214,18 @@ private unsafe void Monitor(AsyncReadState state)
192214 state . ThreadPoolBinding . FreeNativeOverlapped ( overlappedPointer ) ;
193215 }
194216
195- // Clean up the thread pool binding created for the entire operation
196- state . PreAllocatedOverlapped . Dispose ( ) ;
197- state . ThreadPoolBinding . Dispose ( ) ;
217+ // Check whether the directory handle is still valid _before_ we dispose of the state,
218+ // which will invalidate the handle.
219+ bool handleClosed = IsHandleClosed ( state . DirectoryHandle ) ;
220+
221+ // Clean up the state created for the whole operation.
222+ state . Dispose ( ) ;
198223
199224 // Finally, if the handle was for some reason changed or closed during this call,
200225 // then don't throw an exception. Otherwise, it's a valid error.
201- if ( ! IsHandleInvalid ( state . DirectoryHandle ) )
226+ if ( ! handleClosed )
202227 {
203- OnError ( new ErrorEventArgs ( new Win32Exception ( ) ) ) ;
228+ OnError ( new ErrorEventArgs ( new Win32Exception ( win32Error ) ) ) ;
204229 }
205230 }
206231 }
@@ -211,7 +236,7 @@ private void ReadDirectoryChangesCallback(uint errorCode, uint numBytes, AsyncRe
211236 {
212237 try
213238 {
214- if ( IsHandleInvalid ( state . DirectoryHandle ) )
239+ if ( IsHandleClosed ( state . DirectoryHandle ) )
215240 return ;
216241
217242 if ( errorCode != 0 )
@@ -236,13 +261,17 @@ private void ReadDirectoryChangesCallback(uint errorCode, uint numBytes, AsyncRe
236261 if ( state . Session != Volatile . Read ( ref _currentSession ) )
237262 return ;
238263
239- if ( numBytes == 0 )
264+ if ( numBytes == 0 || numBytes > state . BufferByteLength )
240265 {
266+ Debug . Assert ( numBytes == 0 , "ReadDirectoryChangesW returned more bytes than the buffer can hold!" ) ;
241267 NotifyInternalBufferOverflowEvent ( ) ;
242268 }
243269 else
244270 {
245- ParseEventBufferAndNotifyForEach ( new ReadOnlySpan < byte > ( state . Buffer , 0 , ( int ) numBytes ) ) ;
271+ unsafe
272+ {
273+ ParseEventBufferAndNotifyForEach ( new ReadOnlySpan < byte > ( state . Buffer , ( int ) numBytes ) ) ;
274+ }
246275 }
247276 }
248277 finally
@@ -374,26 +403,36 @@ private unsafe void ParseEventBufferAndNotifyForEach(ReadOnlySpan<byte> buffer)
374403 /// </summary>
375404 private sealed class AsyncReadState
376405 {
377- internal AsyncReadState ( int session , byte [ ] buffer , SafeFileHandle handle , ThreadPoolBoundHandle binding , FileSystemWatcher parent )
406+ internal AsyncReadState ( int session , FileSystemWatcher parent , SafeFileHandle directoryHandle )
378407 {
379- Debug . Assert ( buffer != null ) ;
380- Debug . Assert ( buffer . Length > 0 ) ;
381- Debug . Assert ( handle != null ) ;
382- Debug . Assert ( binding != null ) ;
383-
384408 Session = session ;
385- Buffer = buffer ;
386- DirectoryHandle = handle ;
387- ThreadPoolBinding = binding ;
388409 WeakWatcher = new WeakReference < FileSystemWatcher > ( parent ) ;
410+ DirectoryHandle = directoryHandle ;
389411 }
390412
391413 internal int Session { get ; }
392- internal byte [ ] Buffer { get ; }
393- internal SafeFileHandle DirectoryHandle { get ; }
394- internal ThreadPoolBoundHandle ThreadPoolBinding { get ; }
395- internal PreAllocatedOverlapped ? PreAllocatedOverlapped { get ; set ; }
396414 internal WeakReference < FileSystemWatcher > WeakWatcher { get ; }
415+ internal SafeFileHandle DirectoryHandle { get ; set ; }
416+
417+ internal unsafe void * Buffer { get ; set ; }
418+ internal uint BufferByteLength { get ; set ; }
419+
420+ internal ThreadPoolBoundHandle ? ThreadPoolBinding { get ; set ; }
421+
422+ internal PreAllocatedOverlapped ? PreAllocatedOverlapped { get ; set ; }
423+
424+ public void Dispose ( )
425+ {
426+ unsafe
427+ {
428+ NativeMemory . Free ( Buffer ) ;
429+ Buffer = null ;
430+ }
431+
432+ PreAllocatedOverlapped ? . Dispose ( ) ;
433+ ThreadPoolBinding ? . Dispose ( ) ;
434+ DirectoryHandle ? . Dispose ( ) ;
435+ }
397436 }
398437 }
399438}
0 commit comments