Skip to content

Commit

Permalink
Use function pointers in OSX FileSystemWatcher (dotnet#52090)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkotas authored Apr 30, 2021
1 parent 7213840 commit 6b71dd2
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 38 deletions.
35 changes: 14 additions & 21 deletions src/libraries/Common/src/Interop/OSX/Interop.EventStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using CFStringRef = System.IntPtr;
using CFArrayRef = System.IntPtr;
using FSEventStreamRef = System.IntPtr;
using CFIndex = System.IntPtr;
using size_t = System.IntPtr;
using FSEventStreamEventId = System.UInt64;
using CFTimeInterval = System.Double;
Expand Down Expand Up @@ -72,30 +73,22 @@ internal enum FSEventStreamCreateFlags : uint
kFSEventStreamCreateFlagFileEvents = 0x00000010
}

/// <summary>
/// The EventStream callback that will be called for every event batch.
/// </summary>
/// <param name="streamReference">The stream that was created for this callback.</param>
/// <param name="clientCallBackInfo">A pointer to optional context info; otherwise, IntPtr.Zero.</param>
/// <param name="numEvents">The number of paths, events, and IDs. Path[2] corresponds to Event[2] and ID[2], etc.</param>
/// <param name="eventPaths">The paths that have changed somehow, according to their corresponding event.</param>
/// <param name="eventFlags">The events for the corresponding path.</param>
/// <param name="eventIds">The machine-and-disk-drive-unique Event ID for the specific event.</param>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal unsafe delegate void FSEventStreamCallback(
FSEventStreamRef streamReference,
IntPtr clientCallBackInfo,
size_t numEvents,
byte** eventPaths,
FSEventStreamEventFlags* eventFlags,
FSEventStreamEventId* eventIds);
[StructLayout(LayoutKind.Sequential)]
internal struct FSEventStreamContext
{
public CFIndex version;
public IntPtr info;
public IntPtr retainFunc;
public IntPtr releaseFunc;
public IntPtr copyDescription;
}

/// <summary>
/// Internal wrapper to create a new EventStream to listen to events from the core OS (such as File System events).
/// </summary>
/// <param name="allocator">Should be IntPtr.Zero</param>
/// <param name="callback">A callback instance that will be called for every event batch.</param>
/// <param name="context">Should be IntPtr.Zero</param>
/// <param name="context">FSEventStreamContext structure to associate with this stream.</param>
/// <param name="pathsToWatch">A CFArray of the path(s) to watch for events.</param>
/// <param name="sinceWhen">
/// The start point to receive events from. This can be to retrieve historical events or only new events.
Expand All @@ -107,10 +100,10 @@ internal unsafe delegate void FSEventStreamCallback(
/// <returns>On success, returns a pointer to an FSEventStream object; otherwise, returns IntPtr.Zero</returns>
/// <remarks>For *nix systems, the CLR maps ANSI to UTF-8, so be explicit about that</remarks>
[DllImport(Interop.Libraries.CoreServicesLibrary, CharSet = CharSet.Ansi)]
internal static extern SafeEventStreamHandle FSEventStreamCreate(
internal static extern unsafe SafeEventStreamHandle FSEventStreamCreate(
IntPtr allocator,
FSEventStreamCallback callback,
IntPtr context,
delegate* unmanaged<FSEventStreamRef, IntPtr, size_t, byte**, FSEventStreamEventFlags*, FSEventStreamEventId*, void> callback,
FSEventStreamContext* context,
SafeCreateHandle pathsToWatch,
FSEventStreamEventId sinceWhen,
CFTimeInterval latency,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ internal static partial class SystemConfiguration
internal struct SCDynamicStoreContext
{
public CFIndex version;
public IntPtr Info;
public IntPtr RetainFunc;
public IntPtr ReleaseFunc;
public CFStringRef CopyDescriptionFunc;
public IntPtr info;
public IntPtr retainFunc;
public IntPtr releaseFunc;
public IntPtr copyDescription;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,6 @@ private sealed class RunningInstance
// The bitmask of events that we want to send to the user
private readonly FSEventStreamEventFlags _filterFlags;

// Callback delegate for the EventStream events
private readonly Interop.EventStream.FSEventStreamCallback _callback;

// GC handle to keep this running instance rooted
private GCHandle _gcHandle;

Expand Down Expand Up @@ -172,7 +169,6 @@ internal unsafe RunningInstance(
_fullDirectory += "/";
}

_callback = new Interop.EventStream.FSEventStreamCallback(FileSystemEventCallback);
_weakWatcher = new WeakReference<FileSystemWatcher>(watcher);
_includeChildren = includeChildren;
_filterFlags = filter;
Expand Down Expand Up @@ -285,7 +281,7 @@ private void CleanupEventStream()
}
}

internal void Start(CancellationToken cancellationToken)
internal unsafe void Start(CancellationToken cancellationToken)
{
SafeCreateHandle? path = null;
SafeCreateHandle? arrPaths = null;
Expand Down Expand Up @@ -316,11 +312,14 @@ internal void Start(CancellationToken cancellationToken)

cleanupGCHandle = true;

Interop.EventStream.FSEventStreamContext context = default;
context.info = GCHandle.ToIntPtr(_gcHandle);

// Create the event stream for the path and tell the stream to watch for file system events.
SafeEventStreamHandle eventStream = Interop.EventStream.FSEventStreamCreate(
IntPtr.Zero,
_callback,
IntPtr.Zero,
&FileSystemEventCallback,
&context,
arrPaths,
Interop.EventStream.kFSEventStreamEventIdSinceNow,
0.0f,
Expand Down Expand Up @@ -374,36 +373,40 @@ internal void Start(CancellationToken cancellationToken)
}
}

private unsafe void FileSystemEventCallback(
[UnmanagedCallersOnly]
private static unsafe void FileSystemEventCallback(
FSEventStreamRef streamRef,
IntPtr clientCallBackInfo,
size_t numEvents,
byte** eventPaths,
FSEventStreamEventFlags* eventFlags,
FSEventStreamEventId* eventIds)
{
RunningInstance? instance = (RunningInstance?)GCHandle.FromIntPtr(clientCallBackInfo).Target;
Debug.Assert(instance != null);

// Try to get the actual watcher from our weak reference. We maintain a weak reference most of the time
// so as to avoid a rooted cycle that would prevent our processing loop from ever ending
// if the watcher is dropped by the user without being disposed. If we can't get the watcher,
// there's nothing more to do (we can't raise events), so bail.
if (!_weakWatcher.TryGetTarget(out FileSystemWatcher? watcher))
if (!instance._weakWatcher.TryGetTarget(out FileSystemWatcher? watcher))
{
CleanupEventStream();
instance.CleanupEventStream();
return;
}

ExecutionContext? context = _context;
ExecutionContext? context = instance._context;
if (context is null)
{
// Flow suppressed, just run here
ProcessEvents(numEvents.ToInt32(), eventPaths, new Span<FSEventStreamEventFlags>(eventFlags, numEvents.ToInt32()), new Span<FSEventStreamEventId>(eventIds, numEvents.ToInt32()), watcher);
instance.ProcessEvents(numEvents.ToInt32(), eventPaths, new Span<FSEventStreamEventFlags>(eventFlags, numEvents.ToInt32()), new Span<FSEventStreamEventId>(eventIds, numEvents.ToInt32()), watcher);
}
else
{
ExecutionContext.Run(
context,
(object? o) => ((RunningInstance)o!).ProcessEvents(numEvents.ToInt32(), eventPaths, new Span<FSEventStreamEventFlags>(eventFlags, numEvents.ToInt32()), new Span<FSEventStreamEventId>(eventIds, numEvents.ToInt32()), watcher),
this);
instance);
}
}

Expand Down

0 comments on commit 6b71dd2

Please sign in to comment.