Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 0f67204

Browse files
committed
Fix FSW freeing of NativeOverlapped on wrong ThreadPoolBoundHandle
If a FileSystemWatcher is stopped and restarted while processing events, it can end up trying to free an allocated NativeOverlapped on the ThreadPoolBoundHandle associated with the new session even if it was created with the old one. This commit changes the code to store the appropriate binding and directory handle in locals and in the async state object so that the same binding can be used to free as was used to allocate.
1 parent 1ddfc6d commit 0f67204

File tree

1 file changed

+21
-12
lines changed

1 file changed

+21
-12
lines changed

src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,11 @@ private void FinalizeDispose()
108108
// Thread gate holder and constats
109109
private bool _stopListening = false;
110110

111-
private bool IsHandleInvalid
111+
private bool IsHandleInvalid { get { return IsHandleInstanceInvalid(_directoryHandle); } }
112+
113+
private bool IsHandleInstanceInvalid(SafeFileHandle handle)
112114
{
113-
get
114-
{
115-
return (_directoryHandle == null || _directoryHandle.IsInvalid || _directoryHandle.IsClosed);
116-
}
115+
return handle == null || handle.IsInvalid || handle.IsClosed;
117116
}
118117

119118
/// <devdoc>
@@ -132,12 +131,21 @@ private unsafe void Monitor(byte[] buffer)
132131
buffer = AllocateBuffer();
133132
}
134133

134+
// Use a local copy of _threadPoolBinding for the remainder of the method.
135+
// Otherwise, the value of the field could get replaced by a different
136+
// instance if events are stopped and then restarted, and we could end up
137+
// trying to use the new binding with an overlapped created on the old binding.
138+
// Similarly for the directory handle.
139+
ThreadPoolBoundHandle threadPoolBinding = _threadPoolBinding;
140+
SafeFileHandle directoryHandle = _directoryHandle;
141+
135142
// Pass "session" counter to callback:
136143
FSWAsyncResult asyncResult = new FSWAsyncResult();
137144
asyncResult.session = _currentSession;
138145
asyncResult.buffer = buffer;
146+
asyncResult.threadPoolBinding = threadPoolBinding;
139147

140-
NativeOverlapped* overlappedPointer = _threadPoolBinding.AllocateNativeOverlapped(new IOCompletionCallback(this.CompletionStatusChanged), asyncResult, buffer);
148+
NativeOverlapped* overlappedPointer = threadPoolBinding.AllocateNativeOverlapped(new IOCompletionCallback(this.CompletionStatusChanged), asyncResult, buffer);
141149

142150
// Can now call OS:
143151
int size;
@@ -149,12 +157,12 @@ private unsafe void Monitor(byte[] buffer)
149157
// and when we get here from CompletionStatusChanged.
150158
// We might need to take a lock to prevent race absolutely, instead just catch
151159
// ObjectDisposedException from SafeHandle in case it is disposed
152-
if (!IsHandleInvalid)
160+
if (!IsHandleInstanceInvalid(directoryHandle))
153161
{
154162
// An interrupt is possible here
155163
fixed (byte* buffPtr = buffer)
156164
{
157-
ok = UnsafeNativeMethods.ReadDirectoryChangesW(_directoryHandle,
165+
ok = UnsafeNativeMethods.ReadDirectoryChangesW(directoryHandle,
158166
buffPtr,
159167
_internalBufferSize,
160168
_includeSubdirectories ? 1 : 0,
@@ -170,17 +178,17 @@ private unsafe void Monitor(byte[] buffer)
170178
}
171179
catch (ArgumentNullException)
172180
{ //Ignore
173-
Debug.Assert(IsHandleInvalid, "ArgumentNullException from something other than SafeHandle?");
181+
Debug.Assert(IsHandleInstanceInvalid(directoryHandle), "ArgumentNullException from something other than SafeHandle?");
174182
}
175183
finally
176184
{
177185
if (!ok)
178186
{
179-
_threadPoolBinding.FreeNativeOverlapped(overlappedPointer);
187+
threadPoolBinding.FreeNativeOverlapped(overlappedPointer);
180188

181189
// If the handle was for some reason changed or closed during this call, then don't throw an
182190
// exception. Else, it's a valid error.
183-
if (!IsHandleInvalid)
191+
if (!IsHandleInstanceInvalid(directoryHandle))
184192
{
185193
OnError(new ErrorEventArgs(new Win32Exception()));
186194
}
@@ -354,7 +362,7 @@ rename event with the Name field null and then fire the next action.
354362
}
355363
finally
356364
{
357-
_threadPoolBinding.FreeNativeOverlapped(overlappedPointer);
365+
asyncResult.threadPoolBinding.FreeNativeOverlapped(overlappedPointer);
358366
if (!_stopListening)
359367
{
360368
Monitor(asyncResult.buffer);
@@ -368,6 +376,7 @@ private sealed class FSWAsyncResult
368376
{
369377
internal int session;
370378
internal byte[] buffer;
379+
internal ThreadPoolBoundHandle threadPoolBinding;
371380
}
372381

373382
/// <devdoc>

0 commit comments

Comments
 (0)