From 3b70b8a7f615f683e47dab96af4892cb9c388579 Mon Sep 17 00:00:00 2001 From: "Xamarin Public Jenkins (auto-signing)" Date: Tue, 15 Jan 2019 08:32:41 +0000 Subject: [PATCH] Imported Upstream version 5.18.0.239 Former-commit-id: 7b85789fc05cc44b56bcb66c7f08d3bab02d7ee9 --- configure.REMOVED.git-id | 2 +- configure.ac.REMOVED.git-id | 2 +- .../monodroid/System.cs.REMOVED.git-id | 2 +- .../monotouch/System.cs.REMOVED.git-id | 2 +- .../profiles/net_4_x/System.cs.REMOVED.git-id | 2 +- .../src/Interop/OSX/Interop.EventStream.cs | 4 +- .../kernel32/Interop.ReadDirectoryChangesW.cs | 2 +- .../src/System/IO/FileSystemWatcher.OSX.cs | 325 ++++++++++------ .../src/System/IO/FileSystemWatcher.cs | 290 ++++++++++----- .../tests/FileSystemWatcher.File.Create.cs | 39 ++ .../FileSystemWatcher.MultipleWatchers.cs | 351 ++++++++++++++++++ .../tests/FileSystemWatcher.unit.cs | 4 +- .../System.IO.FileSystem.Watcher.Tests.csproj | 1 + .../tests/Utility/FileSystemWatcherTest.cs | 122 ++++-- .../src/System/IO/FileSystem.Unix.cs | 20 +- mcs/build/common/Consts.cs | 2 +- .../Mono.Security.dll.REMOVED.git-id | 2 +- .../System.Configuration.dll.REMOVED.git-id | 2 +- .../System.Core.dll.REMOVED.git-id | 2 +- .../System.IO.Compression.dll | Bin 99328 -> 99328 bytes .../System.Numerics.dll.REMOVED.git-id | 2 +- .../System.Xml.dll.REMOVED.git-id | 2 +- .../System.dll.REMOVED.git-id | 2 +- .../mcs.exe.REMOVED.git-id | 2 +- .../mscorlib.dll.REMOVED.git-id | 2 +- .../Mono.Security.dll.REMOVED.git-id | 2 +- .../System.Configuration.dll.REMOVED.git-id | 2 +- .../System.Core.dll.REMOVED.git-id | 2 +- .../System.IO.Compression.dll | Bin 99328 -> 99328 bytes .../System.Numerics.dll.REMOVED.git-id | 2 +- .../System.Xml.dll.REMOVED.git-id | 2 +- .../System.dll.REMOVED.git-id | 2 +- .../mcs.exe.REMOVED.git-id | 2 +- .../mscorlib.dll.REMOVED.git-id | 2 +- .../Mono.Security.dll.REMOVED.git-id | 2 +- .../System.Configuration.dll.REMOVED.git-id | 2 +- .../System.Core.dll.REMOVED.git-id | 2 +- .../System.IO.Compression.dll | Bin 99328 -> 99328 bytes .../System.Numerics.dll.REMOVED.git-id | 2 +- .../System.Xml.dll.REMOVED.git-id | 2 +- .../System.dll.REMOVED.git-id | 2 +- .../mcs.exe.REMOVED.git-id | 2 +- .../mscorlib.dll.REMOVED.git-id | 2 +- .../Mono.Security.dll.REMOVED.git-id | 2 +- .../System.Configuration.dll.REMOVED.git-id | 2 +- .../System.Core.dll.REMOVED.git-id | 2 +- .../System.IO.Compression.dll | Bin 99328 -> 99328 bytes .../System.Numerics.dll.REMOVED.git-id | 2 +- .../System.Xml.dll.REMOVED.git-id | 2 +- .../System.dll.REMOVED.git-id | 2 +- .../mcs.exe.REMOVED.git-id | 2 +- .../mscorlib.dll.REMOVED.git-id | 2 +- mono/mini/version.h | 2 +- po/mcs/de.gmo | Bin 5406 -> 5406 bytes po/mcs/de.po.REMOVED.git-id | 2 +- po/mcs/es.gmo | Bin 16329 -> 16329 bytes po/mcs/es.po.REMOVED.git-id | 2 +- po/mcs/ja.gmo | Bin 20863 -> 20863 bytes po/mcs/ja.po.REMOVED.git-id | 2 +- po/mcs/mcs.pot | 4 +- po/mcs/pt_BR.gmo | Bin 72806 -> 72806 bytes po/mcs/pt_BR.po.REMOVED.git-id | 2 +- 62 files changed, 953 insertions(+), 295 deletions(-) create mode 100644 external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.MultipleWatchers.cs diff --git a/configure.REMOVED.git-id b/configure.REMOVED.git-id index fb7756be96..5d0e94b95c 100644 --- a/configure.REMOVED.git-id +++ b/configure.REMOVED.git-id @@ -1 +1 @@ -780e08f1c84044d68b8cf86772043410d3356b47 \ No newline at end of file +e3fddfdc54d7204c3e52ef2273ea8a313426467c \ No newline at end of file diff --git a/configure.ac.REMOVED.git-id b/configure.ac.REMOVED.git-id index 0c9124d0eb..81c5f08b2e 100644 --- a/configure.ac.REMOVED.git-id +++ b/configure.ac.REMOVED.git-id @@ -1 +1 @@ -2092fc41e2cc7db82ed0569a412bdf7e09f30285 \ No newline at end of file +f4f0b4d1402862ad9ca37078d37fedfb7dd03bed \ No newline at end of file diff --git a/external/api-snapshot/profiles/monodroid/System.cs.REMOVED.git-id b/external/api-snapshot/profiles/monodroid/System.cs.REMOVED.git-id index 64729d4105..e6bf759e3e 100644 --- a/external/api-snapshot/profiles/monodroid/System.cs.REMOVED.git-id +++ b/external/api-snapshot/profiles/monodroid/System.cs.REMOVED.git-id @@ -1 +1 @@ -0a306482eac9dc254a050e40a50147c9f8a8f319 \ No newline at end of file +a4d41f54aa4e797510b7305e90672d0f0b716d01 \ No newline at end of file diff --git a/external/api-snapshot/profiles/monotouch/System.cs.REMOVED.git-id b/external/api-snapshot/profiles/monotouch/System.cs.REMOVED.git-id index 90ff443c45..9027baaebc 100644 --- a/external/api-snapshot/profiles/monotouch/System.cs.REMOVED.git-id +++ b/external/api-snapshot/profiles/monotouch/System.cs.REMOVED.git-id @@ -1 +1 @@ -f1f7d7c12ac0ebb5ae0ba105d8a40604bbe64b2a \ No newline at end of file +e7cc3043af28842bbfcbfcc3e778a58f8ee6e205 \ No newline at end of file diff --git a/external/api-snapshot/profiles/net_4_x/System.cs.REMOVED.git-id b/external/api-snapshot/profiles/net_4_x/System.cs.REMOVED.git-id index 573132a531..c01074bc7f 100644 --- a/external/api-snapshot/profiles/net_4_x/System.cs.REMOVED.git-id +++ b/external/api-snapshot/profiles/net_4_x/System.cs.REMOVED.git-id @@ -1 +1 @@ -47154482003e653af7b9a47e14d653dd2a666e8d \ No newline at end of file +b081ba1d1e3552cb0f915ccd31f8797bf18c73b5 \ No newline at end of file diff --git a/external/corefx/src/Common/src/Interop/OSX/Interop.EventStream.cs b/external/corefx/src/Common/src/Interop/OSX/Interop.EventStream.cs index b5cbbdb499..7c6e93c761 100644 --- a/external/corefx/src/Common/src/Interop/OSX/Interop.EventStream.cs +++ b/external/corefx/src/Common/src/Interop/OSX/Interop.EventStream.cs @@ -82,12 +82,12 @@ internal static partial class Interop /// The events for the corresponding path. /// The machine-and-disk-drive-unique Event ID for the specific event. [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - internal delegate void FSEventStreamCallback( + internal unsafe delegate void FSEventStreamCallback( FSEventStreamRef streamReference, IntPtr clientCallBackInfo, size_t numEvents, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] - String[] eventPaths, + byte** eventPaths, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] FSEventStreamEventFlags[] eventFlags, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] diff --git a/external/corefx/src/Common/src/Interop/Windows/kernel32/Interop.ReadDirectoryChangesW.cs b/external/corefx/src/Common/src/Interop/Windows/kernel32/Interop.ReadDirectoryChangesW.cs index 84cd715e57..4d4d38f0d2 100644 --- a/external/corefx/src/Common/src/Interop/Windows/kernel32/Interop.ReadDirectoryChangesW.cs +++ b/external/corefx/src/Common/src/Interop/Windows/kernel32/Interop.ReadDirectoryChangesW.cs @@ -15,7 +15,7 @@ internal partial class Interop internal static extern unsafe bool ReadDirectoryChangesW( SafeFileHandle hDirectory, byte[] lpBuffer, - int nBufferLength, + uint nBufferLength, [MarshalAs(UnmanagedType.Bool)] bool bWatchSubtree, int dwNotifyFilter, out int lpBytesReturned, diff --git a/external/corefx/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs b/external/corefx/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs index d86b5781ea..87c6181857 100644 --- a/external/corefx/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs +++ b/external/corefx/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs @@ -2,10 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; +using System.Text; using CFStringRef = System.IntPtr; using FSEventStreamRef = System.IntPtr; @@ -146,8 +148,6 @@ namespace System.IO // The EventStream to listen for events on private SafeEventStreamHandle _eventStream; - // A reference to the RunLoop that we can use to start or stop a Watcher - private CFRunLoopRef _watcherRunLoop; // Callback delegate for the EventStream events private Interop.EventStream.FSEventStreamCallback _callback; @@ -158,7 +158,8 @@ namespace System.IO // Calling RunLoopStop multiple times SegFaults so protect the call to it private bool _stopping; - private object StopLock => this; + + private ExecutionContext _context; internal RunningInstance( FileSystemWatcher watcher, @@ -171,7 +172,6 @@ namespace System.IO Debug.Assert(!cancelToken.IsCancellationRequested); _weakWatcher = new WeakReference(watcher); - _watcherRunLoop = IntPtr.Zero; _fullDirectory = System.IO.Path.GetFullPath(directory); _includeChildren = includeChildren; _filterFlags = filter; @@ -180,21 +180,111 @@ namespace System.IO _stopping = false; } - private void CancellationCallback() + private static class StaticWatcherRunLoopManager { - lock (StopLock) - { - if (!_stopping && _watcherRunLoop != IntPtr.Zero) - { - _stopping = true; + // A reference to the RunLoop that we can use to start or stop a Watcher + private static CFRunLoopRef s_watcherRunLoop = IntPtr.Zero; - // Stop the FS event message pump - Interop.RunLoop.CFRunLoopStop(_watcherRunLoop); + private static int s_scheduledStreamsCount = 0; + + private static readonly object s_lockObject = new object(); + + public static void ScheduleEventStream(SafeEventStreamHandle eventStream) + { + lock (s_lockObject) + { + if (s_watcherRunLoop != IntPtr.Zero) + { + // Schedule the EventStream to run on the thread's RunLoop + s_scheduledStreamsCount++; + Interop.EventStream.FSEventStreamScheduleWithRunLoop(eventStream, s_watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode); + return; + } + + Debug.Assert(s_scheduledStreamsCount == 0); + s_scheduledStreamsCount = 1; + var runLoopStarted = new ManualResetEventSlim(); + new Thread(WatchForFileSystemEventsThreadStart) { IsBackground = true }.Start(new object[] { runLoopStarted, eventStream }); + runLoopStarted.Wait(); + } + } + + public static void UnscheduleFromRunLoop(SafeEventStreamHandle eventStream) + { + Debug.Assert(s_watcherRunLoop != IntPtr.Zero); + lock (s_lockObject) + { + if (s_watcherRunLoop != IntPtr.Zero) + { + // Always unschedule the RunLoop before cleaning up + Interop.EventStream.FSEventStreamUnscheduleFromRunLoop(eventStream, s_watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode); + s_scheduledStreamsCount--; + + if (s_scheduledStreamsCount == 0) + { + // Stop the FS event message pump + Interop.RunLoop.CFRunLoopStop(s_watcherRunLoop); + s_watcherRunLoop = IntPtr.Zero; + } + } + } + } + + private static void WatchForFileSystemEventsThreadStart(object args) + { + var inputArgs = (object[])args; + var runLoopStarted = (ManualResetEventSlim)inputArgs[0]; + var _eventStream = (SafeEventStreamHandle)inputArgs[1]; + // Get this thread's RunLoop + IntPtr runLoop = Interop.RunLoop.CFRunLoopGetCurrent(); + s_watcherRunLoop = runLoop; + Debug.Assert(s_watcherRunLoop != IntPtr.Zero); + + // Retain the RunLoop so that it doesn't get moved or cleaned up before we're done with it. + IntPtr retainResult = Interop.CoreFoundation.CFRetain(runLoop); + Debug.Assert(retainResult == runLoop, "CFRetain is supposed to return the input value"); + + // Schedule the EventStream to run on the thread's RunLoop + Interop.EventStream.FSEventStreamScheduleWithRunLoop(_eventStream, runLoop, Interop.RunLoop.kCFRunLoopDefaultMode); + + runLoopStarted.Set(); + try + { + // Start the OS X RunLoop (a blocking call) that will pump file system changes into the callback function + Interop.RunLoop.CFRunLoopRun(); + } + finally + { + lock (s_lockObject) + { +#if MONO + s_watcherRunLoop = IntPtr.Zero; +#endif + Interop.CoreFoundation.CFRelease(runLoop); + } } } } - internal void Start() + private void CancellationCallback() + { + if (!_stopping && _eventStream != null) + { + _stopping = true; + + try + { + // When we get here, we've requested to stop so cleanup the EventStream and unschedule from the RunLoop + Interop.EventStream.FSEventStreamStop(_eventStream); + } + finally + { + StaticWatcherRunLoopManager.UnscheduleFromRunLoop(_eventStream); + } + } + } + + internal unsafe void Start() { // Make sure _fullPath doesn't contain a link or alias // since the OS will give back the actual, non link'd or alias'd paths @@ -233,6 +323,8 @@ namespace System.IO _callback = new Interop.EventStream.FSEventStreamCallback(FileSystemEventCallback); } + _context = ExecutionContext.Capture(); + // Make sure the OS file buffer(s) are fully flushed so we don't get events from cached I/O Interop.Sys.Sync(); @@ -250,82 +342,32 @@ namespace System.IO throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true); } - // Create and start our watcher thread then wait for the thread to initialize and start - // the RunLoop. We wait for that to prevent this function from returning before the RunLoop - // has a chance to start so that any callers won't race with the background thread's initialization - // and calling Stop, which would attempt to stop a RunLoop that hasn't started yet. - var runLoopStarted = new ManualResetEventSlim(); - new Thread(WatchForFileSystemEventsThreadStart) { IsBackground = true }.Start(runLoopStarted); - runLoopStarted.Wait(); - } + StaticWatcherRunLoopManager.ScheduleEventStream(_eventStream); - private void WatchForFileSystemEventsThreadStart(object arg) - { - var runLoopStarted = (ManualResetEventSlim)arg; - - // Get this thread's RunLoop - _watcherRunLoop = Interop.RunLoop.CFRunLoopGetCurrent(); - Debug.Assert(_watcherRunLoop != IntPtr.Zero); - - // Retain the RunLoop so that it doesn't get moved or cleaned up before we're done with it. - IntPtr retainResult = Interop.CoreFoundation.CFRetain(_watcherRunLoop); - Debug.Assert(retainResult == _watcherRunLoop, "CFRetain is supposed to return the input value"); - - // Schedule the EventStream to run on the thread's RunLoop - Interop.EventStream.FSEventStreamScheduleWithRunLoop(_eventStream, _watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode); - - try - { - bool started = Interop.EventStream.FSEventStreamStart(_eventStream); - - // Notify the StartRaisingEvents call that we are initialized and about to start - // so that it can return and avoid a race-condition around multiple threads calling Stop and Start - runLoopStarted.Set(); - - if (started) + bool started = Interop.EventStream.FSEventStreamStart(_eventStream); + if (!started) + { + // Try to get the Watcher to raise the error event; if we can't do that, just silently exit since the watcher is gone anyway + FileSystemWatcher watcher; + if (_weakWatcher.TryGetTarget(out watcher)) { - // Start the OS X RunLoop (a blocking call) that will pump file system changes into the callback function - Interop.RunLoop.CFRunLoopRun(); - - // When we get here, we've requested to stop so cleanup the EventStream and unschedule from the RunLoop - Interop.EventStream.FSEventStreamStop(_eventStream); - } - else - { - // Try to get the Watcher to raise the error event; if we can't do that, just silently exist since the watcher is gone anyway - FileSystemWatcher watcher; - if (_weakWatcher.TryGetTarget(out watcher)) - { - // An error occurred while trying to start the run loop so fail out - watcher.OnError(new ErrorEventArgs(new IOException(SR.EventStream_FailedToStart, Marshal.GetLastWin32Error()))); - } - } - } - finally - { - // Always unschedule the RunLoop before cleaning up - Interop.EventStream.FSEventStreamUnscheduleFromRunLoop(_eventStream, _watcherRunLoop, Interop.RunLoop.kCFRunLoopDefaultMode); - - // Release the WatcherLoop Core Foundation object. - lock (StopLock) - { - Interop.CoreFoundation.CFRelease(_watcherRunLoop); - _watcherRunLoop = IntPtr.Zero; + // An error occurred while trying to start the run loop so fail out + watcher.OnError(new ErrorEventArgs(new IOException(SR.EventStream_FailedToStart, Marshal.GetLastWin32Error()))); } } } - private void FileSystemEventCallback( - FSEventStreamRef streamRef, - IntPtr clientCallBackInfo, - size_t numEvents, - String[] eventPaths, + private unsafe void FileSystemEventCallback( + FSEventStreamRef streamRef, + IntPtr clientCallBackInfo, + size_t numEvents, + byte** eventPaths, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] Interop.EventStream.FSEventStreamEventFlags[] eventFlags, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] FSEventStreamEventId[] eventIds) { - Debug.Assert((numEvents.ToInt32() == eventPaths.Length) && (numEvents.ToInt32() == eventFlags.Length) && (numEvents.ToInt32() == eventIds.Length)); + Debug.Assert((eventPaths != null) && (numEvents.ToInt32() == eventFlags.Length) && (numEvents.ToInt32() == eventIds.Length)); // 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 @@ -338,18 +380,40 @@ namespace System.IO return; } + ExecutionContext context = _context; + if (context is null) + { + // Flow suppressed, just run here + ProcessEvents(numEvents.ToInt32(), eventPaths, eventFlags, eventIds, watcher); + } + else + { + ExecutionContext.Run( + context, + (object o) => ((RunningInstance)o).ProcessEvents(numEvents.ToInt32(), eventPaths, eventFlags, eventIds, watcher), + this); + } + } + + private unsafe void ProcessEvents(int numEvents, + byte** eventPaths, + Interop.EventStream.FSEventStreamEventFlags[] eventFlags, + FSEventStreamEventId[] eventIds, + FileSystemWatcher watcher) + { // Since renames come in pairs, when we find the first we need to search for the next one. Once we find it, we'll add it to this // list so when the for-loop comes across it, we'll skip it since it's already been processed as part of the original of the pair. List handledRenameEvents = null; + Memory[] events = new Memory[numEvents]; + ParseEvents(); - for (long i = 0; i < numEvents.ToInt32(); i++) + for (long i = 0; i < numEvents; i++) { - Debug.Assert(eventPaths[i].Length > 0, "Empty events are not supported"); - Debug.Assert(eventPaths[i][eventPaths[i].Length - 1] != '/', "Trailing slashes on events is not supported"); + ReadOnlySpan path = events[i].Span; + Debug.Assert(path[path.Length - 1] != '/', "Trailing slashes on events is not supported"); // Match Windows and don't notify us about changes to the Root folder - string path = eventPaths[i]; - if (string.Compare(path, 0, _fullDirectory, 0, path.Length, StringComparison.OrdinalIgnoreCase) == 0) + if (_fullDirectory.Length >= path.Length && path.Equals(_fullDirectory.AsSpan(0, path.Length), StringComparison.OrdinalIgnoreCase)) { continue; } @@ -366,15 +430,15 @@ namespace System.IO // If this event is the second in a rename pair then skip it continue; } - else if (CheckIfPathIsNested(path) && ((eventType = FilterEvents(eventFlags[i], path)) != 0)) + else if (CheckIfPathIsNested(path) && ((eventType = FilterEvents(eventFlags[i])) != 0)) { // The base FileSystemWatcher does a match check against the relative path before combining with // the root dir; however, null is special cased to signify the root dir, so check if we should use that. - string relativePath = null; - if (path.Equals(_fullDirectory, StringComparison.OrdinalIgnoreCase) == false) + ReadOnlySpan relativePath = ReadOnlySpan.Empty; + if (!path.Equals(_fullDirectory, StringComparison.OrdinalIgnoreCase)) { // Remove the root directory to get the relative path - relativePath = path.Remove(0, _fullDirectory.Length); + relativePath = path.Slice(_fullDirectory.Length); } // Raise a notification for the event @@ -393,7 +457,7 @@ namespace System.IO if (((eventType & WatcherChangeTypes.Renamed) > 0)) { // Find the rename that is paired to this rename, which should be the next rename in the list - long pairedId = FindRenameChangePairedChange(i, eventPaths, eventFlags, eventIds); + long pairedId = FindRenameChangePairedChange(i, eventFlags); if (pairedId == long.MinValue) { // Getting here means we have a rename without a pair, meaning it should be a create for the @@ -416,7 +480,7 @@ namespace System.IO { // Remove the base directory prefix and add the paired event to the list of // events to skip and notify the user of the rename - string newPathRelativeName = eventPaths[pairedId].Remove(0, _fullDirectory.Length); + ReadOnlySpan newPathRelativeName = events[pairedId].Span.Slice(_fullDirectory.Length); watcher.NotifyRenameEventArgs(WatcherChangeTypes.Renamed, newPathRelativeName, relativePath); // Create a new list, if necessary, and add the event @@ -428,6 +492,37 @@ namespace System.IO } } } + + ArraySegment underlyingArray; + if (MemoryMarshal.TryGetArray(events[i], out underlyingArray)) + ArrayPool.Shared.Return(underlyingArray.Array); + } + + this._context = ExecutionContext.Capture(); + + void ParseEvents() + { + for (int i = 0; i < events.Length; i++) + { + int byteCount = 0; + Debug.Assert(eventPaths[i] != null); + byte* temp = eventPaths[i]; + + // Finds the position of null character. + while (*temp != 0) + { + temp++; + byteCount++; + } + + Debug.Assert(byteCount > 0, "Empty events are not supported"); + events[i] = new Memory(ArrayPool.Shared.Rent(Encoding.UTF8.GetMaxCharCount(byteCount))); + int charCount; + + // Converting an array of bytes to UTF-8 char array + charCount = Encoding.UTF8.GetChars(new ReadOnlySpan(eventPaths[i], byteCount), events[i].Span); + events[i] = events[i].Slice(0, charCount); + } } } @@ -435,7 +530,7 @@ namespace System.IO /// Compares the given event flags to the filter flags and returns which event (if any) corresponds /// to those flags. /// - private WatcherChangeTypes FilterEvents(Interop.EventStream.FSEventStreamEventFlags eventFlags, string fullPath) + private WatcherChangeTypes FilterEvents(Interop.EventStream.FSEventStreamEventFlags eventFlags) { const Interop.EventStream.FSEventStreamEventFlags changedFlags = Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemInodeMetaMod | Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemFinderInfoMod | @@ -487,30 +582,24 @@ namespace System.IO IsFlagSet(flags, Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagUnmount)); } - private bool CheckIfPathIsNested(string eventPath) + private bool CheckIfPathIsNested(ReadOnlySpan eventPath) { - bool doesPathPass = true; - // If we shouldn't include subdirectories, check if this path's parent is the watch directory - if (_includeChildren == false) - { - // Check if the parent is the root. If so, then we'll continue processing based on the name. - // If it isn't, then this will be set to false and we'll skip the name processing since it's irrelevant. - string parent = System.IO.Path.GetDirectoryName(eventPath); - doesPathPass = (string.Compare(parent, 0, _fullDirectory, 0, parent.Length, StringComparison.OrdinalIgnoreCase) == 0); - } - - return doesPathPass; + // Check if the parent is the root. If so, then we'll continue processing based on the name. + // If it isn't, then this will be set to false and we'll skip the name processing since it's irrelevant. +#if MONO + return _includeChildren || _fullDirectory.AsSpan().StartsWith(System.IO.Path.GetDirectoryName(eventPath.ToString()), StringComparison.OrdinalIgnoreCase); +#else + return _includeChildren || _fullDirectory.AsSpan().StartsWith(System.IO.Path.GetDirectoryName(eventPath), StringComparison.OrdinalIgnoreCase); +#endif } private long FindRenameChangePairedChange( long currentIndex, - String[] eventPaths, - Interop.EventStream.FSEventStreamEventFlags[] eventFlags, - FSEventStreamEventId[] eventIds) + Interop.EventStream.FSEventStreamEventFlags[] eventFlags) { // Start at one past the current index and try to find the next Rename item, which should be the old path. - for (long i = currentIndex + 1; i < eventPaths.Length; i++) + for (long i = currentIndex + 1; i < eventFlags.Length; i++) { if (IsFlagSet(eventFlags[i], Interop.EventStream.FSEventStreamEventFlags.kFSEventStreamEventFlagItemRenamed)) { @@ -527,12 +616,26 @@ namespace System.IO return (value & flags) == value; } - private static bool DoesItemExist(string path, bool isFile) + private static bool DoesItemExist(ReadOnlySpan path, bool isFile) { - if (isFile) - return File.Exists(path); - else - return Directory.Exists(path); + if (path.IsEmpty || path.Length == 0) + return false; + +#if MONO + if (!isFile) + return Directory.Exists(path.ToString()); + + return path[path.Length - 1] == '/' + ? false + : File.Exists(path.ToString()); +#else + if (!isFile) + return FileSystem.DirectoryExists(path); + + return PathInternal.IsDirectorySeparator(path[path.Length - 1]) + ? false + : FileSystem.FileExists(path); +#endif } } } diff --git a/external/corefx/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.cs b/external/corefx/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.cs index f0f9e0a994..39cb7412ac 100644 --- a/external/corefx/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.cs +++ b/external/corefx/src/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -25,15 +28,12 @@ namespace System.IO /// public partial class FileSystemWatcher : Component, ISupportInitialize { - /// - /// Private instance variables - /// + // Filters collection + private readonly NormalizedFilterCollection _filters = new NormalizedFilterCollection(); + // Directory being monitored private string _directory; - // Filter for name matching - private string _filter; - // The watch filter for the API call. private const NotifyFilters c_defaultNotifyFilters = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; private NotifyFilters _notifyFilters = c_defaultNotifyFilters; @@ -48,11 +48,10 @@ namespace System.IO private bool _initializing = false; // Buffer size - private int _internalBufferSize = 8192; + private uint _internalBufferSize = 8192; // Used for synchronization private bool _disposed; - private ISynchronizeInvoke _synchronizingObject; // Event handlers private FileSystemEventHandler _onChangedHandler = null; @@ -89,15 +88,16 @@ namespace System.IO public FileSystemWatcher() { _directory = string.Empty; - _filter = "*"; } /// /// Initializes a new instance of the class, /// given the specified directory to monitor. /// - public FileSystemWatcher(string path) : this(path, "*") + public FileSystemWatcher(string path) { + CheckPathValidity(path); + _directory = path; } /// @@ -106,21 +106,9 @@ namespace System.IO /// public FileSystemWatcher(string path, string filter) { - if (path == null) - throw new ArgumentNullException(nameof(path)); - - // Early check for directory parameter so that an exception can be thrown as early as possible. - if (path.Length == 0) - throw new ArgumentException(SR.Format(SR.InvalidDirName, path), nameof(path)); - - if (!Directory.Exists(path)) - throw new ArgumentException(SR.Format(SR.InvalidDirName_NotExists, path), nameof(path)); - + CheckPathValidity(path); _directory = path; - _filter = filter ?? throw new ArgumentNullException(nameof(filter)); - - if (_filter == "*.*") - _filter = "*"; + Filter = filter ?? throw new ArgumentNullException(nameof(filter)); } /// @@ -146,6 +134,8 @@ namespace System.IO } } + public Collection Filters => _filters; + /// /// Gets or sets a value indicating whether the component is enabled. /// @@ -187,20 +177,12 @@ namespace System.IO { get { - return _filter; + return Filters.Count == 0 ? "*" : Filters[0]; } set { - if (string.IsNullOrEmpty(value)) - { - // Skip the string compare for "*" since it has no case-insensitive representation that differs from - // the case-sensitive representation. - _filter = "*"; - } - else if (!string.Equals(_filter, value, PathInternal.StringComparison)) - { - _filter = value == "*.*" ? "*" : value; - } + Filters.Clear(); + Filters.Add(value); } } @@ -231,7 +213,7 @@ namespace System.IO { get { - return _internalBufferSize; + return (int)_internalBufferSize; } set { @@ -243,7 +225,7 @@ namespace System.IO } else { - _internalBufferSize = value; + _internalBufferSize = (uint)value; } Restart(); @@ -284,7 +266,7 @@ namespace System.IO if (!Directory.Exists(value)) throw new ArgumentException(SR.Format(SR.InvalidDirName_NotExists, value), nameof(Path)); - + _directory = value; Restart(); } @@ -367,8 +349,6 @@ namespace System.IO } } - /// - /// protected override void Dispose(bool disposing) { try @@ -398,66 +378,100 @@ namespace System.IO } } - /// - /// Sees if the name given matches the name filter we have. - /// - /// - private bool MatchPattern(string relativePath) + private static void CheckPathValidity(string path) { - ReadOnlySpan name = IO.Path.GetFileName(relativePath.AsSpan()); - return name.Length > 0 - ? FileSystemName.MatchesSimpleExpression(_filter, name, ignoreCase: !PathInternal.IsCaseSensitive) - : false; + if (path == null) + throw new ArgumentNullException(nameof(path)); + + // Early check for directory parameter so that an exception can be thrown as early as possible. + if (path.Length == 0) + throw new ArgumentException(SR.Format(SR.InvalidDirName, path), nameof(path)); + + if (!Directory.Exists(path)) + throw new ArgumentException(SR.Format(SR.InvalidDirName_NotExists, path), nameof(path)); } - /// - /// Raises the event to each handler in the list. - /// - /// + /// + /// Sees if the name given matches the name filter we have. + /// + private bool MatchPattern(ReadOnlySpan relativePath) + { + ReadOnlySpan name = IO.Path.GetFileName(relativePath); + if (name.Length == 0) + return false; + + string[] filters = _filters.GetFilters(); + if (filters.Length == 0) + return true; + + foreach (string filter in filters) + { + if (FileSystemName.MatchesSimpleExpression(filter, name, ignoreCase: !PathInternal.IsCaseSensitive)) + return true; + } + + return false; + } + + /// + /// Raises the event to each handler in the list. + /// private void NotifyInternalBufferOverflowEvent() { _onErrorHandler?.Invoke(this, new ErrorEventArgs( new InternalBufferOverflowException(SR.Format(SR.FSW_BufferOverflow, _directory)))); } - /// - /// Raises the event to each handler in the list. - /// - /// - private void NotifyRenameEventArgs(WatcherChangeTypes action, string name, string oldName) + /// + /// Raises the event to each handler in the list. + /// + private void NotifyRenameEventArgs(WatcherChangeTypes action, ReadOnlySpan name, ReadOnlySpan oldName) { // filter if there's no handler or neither new name or old name match a specified pattern RenamedEventHandler handler = _onRenamedHandler; if (handler != null && (MatchPattern(name) || MatchPattern(oldName))) { - handler(this, new RenamedEventArgs(action, _directory, name, oldName)); + handler(this, new RenamedEventArgs(action, _directory, name.IsEmpty ? null : name.ToString(), oldName.IsEmpty ? null : oldName.ToString())); } } - /// - /// Raises the event to each handler in the list. - /// - /// - private void NotifyFileSystemEventArgs(WatcherChangeTypes changeType, string name) + private FileSystemEventHandler GetHandler(WatcherChangeTypes changeType) { - FileSystemEventHandler handler = null; switch (changeType) { case WatcherChangeTypes.Created: - handler = _onCreatedHandler; - break; + return _onCreatedHandler; case WatcherChangeTypes.Deleted: - handler = _onDeletedHandler; - break; + return _onDeletedHandler; case WatcherChangeTypes.Changed: - handler = _onChangedHandler; - break; - default: - Debug.Fail("Unknown FileSystemEvent change type! Value: " + changeType); - break; + return _onChangedHandler; } + Debug.Fail("Unknown FileSystemEvent change type! Value: " + changeType); + return null; + } + + /// + /// Raises the event to each handler in the list. + /// + private void NotifyFileSystemEventArgs(WatcherChangeTypes changeType, ReadOnlySpan name) + { + FileSystemEventHandler handler = GetHandler(changeType); + + if (handler != null && MatchPattern(name.IsEmpty ? _directory : name)) + { + handler(this, new FileSystemEventArgs(changeType, _directory, name.IsEmpty ? null : name.ToString())); + } + } + + /// + /// Raises the event to each handler in the list. + /// + private void NotifyFileSystemEventArgs(WatcherChangeTypes changeType, string name) + { + FileSystemEventHandler handler = GetHandler(changeType); + if (handler != null && MatchPattern(string.IsNullOrEmpty(name) ? _directory : name)) { handler(this, new FileSystemEventArgs(changeType, _directory, name)); @@ -560,9 +574,12 @@ namespace System.IO tcs.TrySetResult(new WaitForChangedResult(e.ChangeType, e.Name, oldName: null, timedOut: false)); } }; - if ((changeType & WatcherChangeTypes.Created) != 0) Created += fseh; - if ((changeType & WatcherChangeTypes.Deleted) != 0) Deleted += fseh; - if ((changeType & WatcherChangeTypes.Changed) != 0) Changed += fseh; + if ((changeType & WatcherChangeTypes.Created) != 0) + Created += fseh; + if ((changeType & WatcherChangeTypes.Deleted) != 0) + Deleted += fseh; + if ((changeType & WatcherChangeTypes.Changed) != 0) + Changed += fseh; } if ((changeType & WatcherChangeTypes.Renamed) != 0) { @@ -600,9 +617,12 @@ namespace System.IO } if (fseh != null) { - if ((changeType & WatcherChangeTypes.Changed) != 0) Changed -= fseh; - if ((changeType & WatcherChangeTypes.Deleted) != 0) Deleted -= fseh; - if ((changeType & WatcherChangeTypes.Created) != 0) Created -= fseh; + if ((changeType & WatcherChangeTypes.Changed) != 0) + Changed -= fseh; + if ((changeType & WatcherChangeTypes.Deleted) != 0) + Deleted -= fseh; + if ((changeType & WatcherChangeTypes.Created) != 0) + Created -= fseh; } } @@ -656,17 +676,7 @@ namespace System.IO } } - public ISynchronizeInvoke SynchronizingObject - { - get - { - return _synchronizingObject; - } - set - { - _synchronizingObject = value; - } - } + public ISynchronizeInvoke SynchronizingObject { get; set; } public void BeginInit() { @@ -688,5 +698,101 @@ namespace System.IO { return _initializing || DesignMode; } + + private sealed class NormalizedFilterCollection : Collection + { + internal NormalizedFilterCollection() : base(new ImmutableStringList()) + { + } + + protected override void InsertItem(int index, string item) + { + base.InsertItem(index, string.IsNullOrEmpty(item) || item == "*.*" ? "*" : item); + } + + protected override void SetItem(int index, string item) + { + base.SetItem(index, string.IsNullOrEmpty(item) || item == "*.*" ? "*" : item); + } + + internal string[] GetFilters() => ((ImmutableStringList)Items).Items; + + /// + /// List that maintains its underlying data in an immutable array, such that the list + /// will never modify an array returned from its Items property. This is to allow + /// the array to be enumerated safely while another thread might be concurrently mutating + /// the collection. + /// + private sealed class ImmutableStringList : IList + { + public string[] Items = Array.Empty(); + + public string this[int index] + { + get + { + string[] items = Items; + if ((uint)index >= (uint)items.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + return items[index]; + } + set + { + string[] clone = (string[])Items.Clone(); + clone[index] = value; + Items = clone; + } + } + + public int Count => Items.Length; + + public bool IsReadOnly => false; + + public void Add(string item) + { + // Collection doesn't use this method. + throw new NotSupportedException(); + } + + public void Clear() => Items = Array.Empty(); + + public bool Contains(string item) => Array.IndexOf(Items, item) != -1; + + public void CopyTo(string[] array, int arrayIndex) => Items.CopyTo(array, arrayIndex); + + public IEnumerator GetEnumerator() => ((IEnumerable)Items).GetEnumerator(); + + public int IndexOf(string item) => Array.IndexOf(Items, item); + + public void Insert(int index, string item) + { + string[] items = Items; + string[] newItems = new string[items.Length + 1]; + items.AsSpan(0, index).CopyTo(newItems); + items.AsSpan(index).CopyTo(newItems.AsSpan(index + 1)); + newItems[index] = item; + Items = newItems; + } + + public bool Remove(string item) + { + // Collection doesn't use this method. + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + string[] items = Items; + string[] newItems = new string[items.Length - 1]; + items.AsSpan(0, index).CopyTo(newItems); + items.AsSpan(index + 1).CopyTo(newItems.AsSpan(index)); + Items = newItems; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } } -} +} \ No newline at end of file diff --git a/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.Create.cs b/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.Create.cs index b14449b3c8..765ef7aaa3 100644 --- a/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.Create.cs +++ b/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.File.Create.cs @@ -26,6 +26,45 @@ namespace System.IO.Tests } } + [Fact] + [OuterLoop] + public void FileSystemWatcher_File_Create_EnablingDisablingNotAffectRaisingEvent() + { + ExecuteWithRetry(() => + { + using (var testDirectory = new TempDirectory(GetTestFilePath())) + using (var watcher = new FileSystemWatcher(testDirectory.Path)) + { + string fileName = Path.Combine(testDirectory.Path, "file"); + watcher.Filter = Path.GetFileName(fileName); + + int numberOfRaisedEvents = 0; + AutoResetEvent autoResetEvent = new AutoResetEvent(false); + FileSystemEventHandler handler = (o, e) => + { + Interlocked.Increment(ref numberOfRaisedEvents); + autoResetEvent.Set(); + }; + + watcher.Created += handler; + + for (int i = 0; i < 100; i++) + { + watcher.EnableRaisingEvents = true; + watcher.EnableRaisingEvents = false; + } + + watcher.EnableRaisingEvents = true; + + // this should raise one and only one event + File.Create(fileName).Dispose(); + Assert.True(autoResetEvent.WaitOne(WaitForExpectedEventTimeout_NoRetry)); + Assert.False(autoResetEvent.WaitOne(SubsequentExpectedWait)); + Assert.True(numberOfRaisedEvents == 1); + } + }); + } + [Fact] public void FileSystemWatcher_File_Create_ForcedRestart() { diff --git a/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.MultipleWatchers.cs b/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.MultipleWatchers.cs new file mode 100644 index 0000000000..a965a18119 --- /dev/null +++ b/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.MultipleWatchers.cs @@ -0,0 +1,351 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Tests +{ + public class FileSystemWatcher_Multiple_Test : FileSystemWatcherTest + { + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "#34017")] + [OuterLoop] + [Fact] + public void FileSystemWatcher_File_Create_ExecutionContextFlowed() + { + ExecuteWithRetry(() => + { + using (var watcher1 = new FileSystemWatcher(TestDirectory)) + using (var watcher2 = new FileSystemWatcher(TestDirectory)) + { + string fileName = Path.Combine(TestDirectory, "file"); + watcher1.Filter = Path.GetFileName(fileName); + watcher2.Filter = Path.GetFileName(fileName); + + var local = new AsyncLocal(); + + var tcs1 = new TaskCompletionSource(); + var tcs2 = new TaskCompletionSource(); + watcher1.Created += (s, e) => tcs1.SetResult(local.Value); + watcher2.Created += (s, e) => tcs2.SetResult(local.Value); + + local.Value = 42; + watcher1.EnableRaisingEvents = true; + local.Value = 84; + watcher2.EnableRaisingEvents = true; + local.Value = 168; + + File.Create(fileName).Dispose(); + Task.WaitAll(new[] { tcs1.Task, tcs2.Task }, WaitForExpectedEventTimeout); + + Assert.Equal(42, tcs1.Task.Result); + Assert.Equal(84, tcs2.Task.Result); + } + }); + } + + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "#34017")] + [OuterLoop] + [Fact] + public void FileSystemWatcher_File_Create_SuppressedExecutionContextHandled() + { + ExecuteWithRetry(() => + { + using (var watcher1 = new FileSystemWatcher(TestDirectory)) + { + string fileName = Path.Combine(TestDirectory, "file"); + watcher1.Filter = Path.GetFileName(fileName); + + var local = new AsyncLocal(); + + var tcs1 = new TaskCompletionSource(); + watcher1.Created += (s, e) => tcs1.SetResult(local.Value); + + local.Value = 42; + + ExecutionContext.SuppressFlow(); + try + { + watcher1.EnableRaisingEvents = true; + } + finally + { + ExecutionContext.RestoreFlow(); + } + + File.Create(fileName).Dispose(); + tcs1.Task.Wait(WaitForExpectedEventTimeout); + + Assert.Equal(0, tcs1.Task.Result); + } + }); + } + + [OuterLoop] + [Fact] + public void FileSystemWatcher_File_Create_NotAffectEachOther() + { + ExecuteWithRetry(() => + { + using (var watcher1 = new FileSystemWatcher(TestDirectory)) + using (var watcher2 = new FileSystemWatcher(TestDirectory)) + using (var watcher3 = new FileSystemWatcher(TestDirectory)) + { + string fileName = Path.Combine(TestDirectory, "file"); + watcher1.Filter = Path.GetFileName(fileName); + watcher2.Filter = Path.GetFileName(fileName); + watcher3.Filter = Path.GetFileName(fileName); + + AutoResetEvent autoResetEvent1 = WatchCreated(watcher1, new[] { fileName }).EventOccured; + AutoResetEvent autoResetEvent2 = WatchCreated(watcher2, new[] { fileName }).EventOccured; + AutoResetEvent autoResetEvent3 = WatchCreated(watcher3, new[] { fileName }).EventOccured; + + watcher1.EnableRaisingEvents = true; + watcher2.EnableRaisingEvents = true; + watcher3.EnableRaisingEvents = true; + + File.Create(fileName).Dispose(); + Assert.True(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent2, autoResetEvent3 }, WaitForExpectedEventTimeout_NoRetry)); + + File.Delete(fileName); + watcher1.EnableRaisingEvents = false; + + File.Create(fileName).Dispose(); + Assert.False(autoResetEvent1.WaitOne(WaitForUnexpectedEventTimeout)); + Assert.True(WaitHandle.WaitAll(new[] { autoResetEvent2, autoResetEvent3 }, WaitForExpectedEventTimeout_NoRetry)); + } + }); + } + + [OuterLoop] + [Fact] + public void FileSystemWatcher_File_Create_WatchOwnPath() + { + ExecuteWithRetry(() => + { + using (var dir = new TempDirectory(GetTestFilePath())) + using (var dir1 = new TempDirectory(Path.Combine(dir.Path, "dir1"))) + using (var dir2 = new TempDirectory(Path.Combine(dir.Path, "dir2"))) + using (var watcher1 = new FileSystemWatcher(dir1.Path, "*")) + using (var watcher2 = new FileSystemWatcher(dir2.Path, "*")) + { + string fileName1 = Path.Combine(dir1.Path, "file"); + string fileName2 = Path.Combine(dir2.Path, "file"); + + AutoResetEvent autoResetEvent1 = WatchCreated(watcher1, new[] { fileName1 }).EventOccured; + AutoResetEvent autoResetEvent2 = WatchCreated(watcher2, new[] { fileName2 }).EventOccured; + + watcher1.EnableRaisingEvents = true; + watcher2.EnableRaisingEvents = true; + + File.Create(fileName1).Dispose(); + Assert.True(autoResetEvent1.WaitOne(WaitForExpectedEventTimeout_NoRetry)); + Assert.False(autoResetEvent2.WaitOne(WaitForUnexpectedEventTimeout)); + + File.Create(fileName2).Dispose(); + Assert.True(autoResetEvent2.WaitOne(WaitForExpectedEventTimeout_NoRetry)); + Assert.False(autoResetEvent1.WaitOne(WaitForUnexpectedEventTimeout)); + } + }); + } + + [OuterLoop] + [Theory] + [InlineData(true)] + [InlineData(false)] + public void FileSystemWatcher_File_Create_ForceLoopRestart(bool useExistingWatchers) + { + ExecuteWithRetry(() => + { + FileSystemWatcher[] watchers = new FileSystemWatcher[64]; + FileSystemWatcher[] watchers1 = new FileSystemWatcher[64]; + + try + { + string fileName = Path.Combine(TestDirectory, "file"); + AutoResetEvent[] autoResetEvents = new AutoResetEvent[64]; + for (var i = 0; i < watchers.Length; i++) + { + watchers[i] = new FileSystemWatcher(TestDirectory); + watchers[i].Filter = Path.GetFileName(fileName); + autoResetEvents[i] = WatchCreated(watchers[i], new[] { fileName }).EventOccured; + watchers[i].EnableRaisingEvents = true; + } + + File.Create(fileName).Dispose(); + Assert.True(WaitHandle.WaitAll(autoResetEvents, WaitForExpectedEventTimeout_NoRetry)); + + File.Delete(fileName); + for (var i = 0; i < watchers.Length; i++) + { + watchers[i].EnableRaisingEvents = false; + } + + File.Create(fileName).Dispose(); + Assert.False(WaitHandle.WaitAll(autoResetEvents, WaitForUnexpectedEventTimeout)); + + File.Delete(fileName); + + if (useExistingWatchers) + { + for (var i = 0; i < watchers.Length; i++) + { + watchers[i].EnableRaisingEvents = true; + } + + File.Create(fileName).Dispose(); + Assert.True(WaitHandle.WaitAll(autoResetEvents, WaitForExpectedEventTimeout_NoRetry)); + } + else + { + AutoResetEvent[] autoResetEvents1 = new AutoResetEvent[64]; + for (var i = 0; i < watchers1.Length; i++) + { + watchers1[i] = new FileSystemWatcher(TestDirectory); + watchers1[i].Filter = Path.GetFileName(fileName); + autoResetEvents1[i] = WatchCreated(watchers1[i], new[] { fileName }).EventOccured; + watchers1[i].EnableRaisingEvents = true; + } + + File.Create(fileName).Dispose(); + Assert.True(WaitHandle.WaitAll(autoResetEvents1, WaitForExpectedEventTimeout_NoRetry)); + } + } + finally + { + for (var i = 0; i < watchers.Length; i++) + { + watchers[i]?.Dispose(); + watchers1[i]?.Dispose(); + } + } + }); + } + + [OuterLoop] + [Fact] + public void FileSystemWatcher_File_Changed_NotAffectEachOther() + { + ExecuteWithRetry(() => + { + using (var testDirectory = new TempDirectory(GetTestFilePath())) + using (var file = new TempFile(Path.Combine(testDirectory.Path, "file"))) + using (var otherFile = new TempFile(Path.Combine(testDirectory.Path, "otherfile"))) + using (var watcher1 = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path))) + using (var watcher2 = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path))) + using (var watcher3 = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(otherFile.Path))) + { + AutoResetEvent autoResetEvent1 = WatchChanged(watcher1, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured; + AutoResetEvent autoResetEvent2 = WatchChanged(watcher2, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured; + AutoResetEvent autoResetEvent3 = WatchChanged(watcher3, new[] { Path.Combine(testDirectory.Path, "otherfile") }).EventOccured; + + watcher1.EnableRaisingEvents = true; + watcher2.EnableRaisingEvents = true; + watcher3.EnableRaisingEvents = true; + + Directory.SetLastWriteTime(file.Path, DateTime.Now + TimeSpan.FromSeconds(10)); + Assert.True(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent2 }, WaitForExpectedEventTimeout_NoRetry)); + Assert.False(autoResetEvent3.WaitOne(WaitForUnexpectedEventTimeout)); + + Directory.SetLastWriteTime(otherFile.Path, DateTime.Now + TimeSpan.FromSeconds(10)); + Assert.False(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent2 }, WaitForUnexpectedEventTimeout)); + Assert.True(autoResetEvent3.WaitOne(WaitForExpectedEventTimeout_NoRetry)); + + watcher1.EnableRaisingEvents = false; + + Directory.SetLastWriteTime(file.Path, DateTime.Now + TimeSpan.FromSeconds(10)); + Assert.False(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent3 }, WaitForUnexpectedEventTimeout)); + Assert.True(autoResetEvent2.WaitOne(WaitForExpectedEventTimeout_NoRetry)); + + Directory.SetLastWriteTime(otherFile.Path, DateTime.Now + TimeSpan.FromSeconds(10)); + Assert.False(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent2 }, WaitForUnexpectedEventTimeout)); + Assert.True(autoResetEvent3.WaitOne(WaitForExpectedEventTimeout_NoRetry)); + } + }); + } + + [OuterLoop] + [Fact] + public void FileSystemWatcher_File_Delet_NotAffectEachOther() + { + ExecuteWithRetry(() => + { + using (var watcher1 = new FileSystemWatcher(TestDirectory)) + using (var watcher2 = new FileSystemWatcher(TestDirectory)) + using (var watcher3 = new FileSystemWatcher(TestDirectory)) + { + string fileName = Path.Combine(TestDirectory, "file"); + File.Create(fileName).Dispose(); + + watcher1.Filter = Path.GetFileName(fileName); + watcher2.Filter = Path.GetFileName(fileName); + watcher3.Filter = Path.GetFileName(fileName); + + AutoResetEvent autoResetEvent1 = WatchDeleted(watcher1, new[] { fileName }).EventOccured; + AutoResetEvent autoResetEvent2 = WatchDeleted(watcher2, new[] { fileName }).EventOccured; + AutoResetEvent autoResetEvent3 = WatchDeleted(watcher3, new[] { fileName }).EventOccured; + + watcher1.EnableRaisingEvents = true; + watcher2.EnableRaisingEvents = true; + watcher3.EnableRaisingEvents = true; + + File.Delete(fileName); + Assert.True(WaitHandle.WaitAll(new[] { autoResetEvent1, autoResetEvent2, autoResetEvent3 }, WaitForExpectedEventTimeout_NoRetry)); + + File.Create(fileName).Dispose(); + watcher1.EnableRaisingEvents = false; + + File.Delete(fileName); + Assert.False(autoResetEvent1.WaitOne(WaitForUnexpectedEventTimeout)); + Assert.True(WaitHandle.WaitAll(new[] { autoResetEvent2, autoResetEvent3 }, WaitForExpectedEventTimeout_NoRetry)); + } + }); + } + + [OuterLoop] + [Fact] + [PlatformSpecific(TestPlatforms.OSX)] + [SkipOnTargetFramework(TargetFrameworkMonikers.Mono)] + public void FileSystemWatcher_File_Rename_NotAffectEachOther() + { + ExecuteWithRetry(() => + { + using (var testDirectory = new TempDirectory(GetTestFilePath())) + using (var file = new TempFile(Path.Combine(testDirectory.Path, "file"))) + using (var watcher1 = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path))) + using (var watcher2 = new FileSystemWatcher(testDirectory.Path, Path.GetFileName(file.Path))) + { + AutoResetEvent autoResetEvent1_created = WatchCreated(watcher1, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured; + AutoResetEvent autoResetEvent1_deleted = WatchDeleted(watcher1, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured; + AutoResetEvent autoResetEvent2_created = WatchCreated(watcher2, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured; + AutoResetEvent autoResetEvent2_deleted = WatchDeleted(watcher2, new[] { Path.Combine(testDirectory.Path, "file") }).EventOccured; + + watcher1.EnableRaisingEvents = true; + watcher2.EnableRaisingEvents = true; + + string filePath = file.Path; + string filePathRenamed = file.Path + "_renamed"; + + File.Move(filePath, filePathRenamed); + Assert.True(WaitHandle.WaitAll( + new[] { autoResetEvent1_created, autoResetEvent1_deleted, autoResetEvent2_created, autoResetEvent2_deleted }, + WaitForExpectedEventTimeout_NoRetry)); + + File.Move(filePathRenamed, filePath); + watcher1.EnableRaisingEvents = false; + + File.Move(filePath, filePathRenamed); + Assert.False(WaitHandle.WaitAll( + new[] { autoResetEvent1_created, autoResetEvent1_deleted }, + WaitForUnexpectedEventTimeout)); + Assert.True(WaitHandle.WaitAll( + new[] { autoResetEvent2_created, autoResetEvent2_deleted }, + WaitForExpectedEventTimeout_NoRetry)); + } + }); + } + } +} diff --git a/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.unit.cs b/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.unit.cs index cce848e614..5035709528 100644 --- a/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.unit.cs +++ b/external/corefx/src/System.IO.FileSystem.Watcher/tests/FileSystemWatcher.unit.cs @@ -474,7 +474,7 @@ namespace System.IO.Tests using (var file = new TempFile(Path.Combine(dir.Path, "file"))) using (var fsw = new FileSystemWatcher(dir.Path)) { - AutoResetEvent eventOccurred = WatchRenamed(fsw); + AutoResetEvent eventOccurred = WatchRenamed(fsw).EventOccured; string newPath = Path.Combine(dir.Path, "newPath"); @@ -626,7 +626,7 @@ namespace System.IO.Tests using (var dir = new TempDirectory(Path.Combine(testDirectory.Path, "dir"))) using (var fsw = new FileSystemWatcher(dir.Path)) { - AutoResetEvent are = WatchCreated(fsw); + AutoResetEvent are = WatchCreated(fsw).EventOccured; fsw.Filter = "*"; fsw.EnableRaisingEvents = true; diff --git a/external/corefx/src/System.IO.FileSystem.Watcher/tests/System.IO.FileSystem.Watcher.Tests.csproj b/external/corefx/src/System.IO.FileSystem.Watcher/tests/System.IO.FileSystem.Watcher.Tests.csproj index 2b4989a1c1..0bb15ae4ae 100644 --- a/external/corefx/src/System.IO.FileSystem.Watcher/tests/System.IO.FileSystem.Watcher.Tests.csproj +++ b/external/corefx/src/System.IO.FileSystem.Watcher/tests/System.IO.FileSystem.Watcher.Tests.csproj @@ -30,6 +30,7 @@ + diff --git a/external/corefx/src/System.IO.FileSystem.Watcher/tests/Utility/FileSystemWatcherTest.cs b/external/corefx/src/System.IO.FileSystem.Watcher/tests/Utility/FileSystemWatcherTest.cs index 6614b83f67..0ce6ebb12b 100644 --- a/external/corefx/src/System.IO.FileSystem.Watcher/tests/Utility/FileSystemWatcherTest.cs +++ b/external/corefx/src/System.IO.FileSystem.Watcher/tests/Utility/FileSystemWatcherTest.cs @@ -4,14 +4,14 @@ using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Xunit; +using Xunit.Sdk; namespace System.IO.Tests { - public abstract class FileSystemWatcherTest : FileCleanupTestBase + public abstract partial class FileSystemWatcherTest : FileCleanupTestBase { // Events are reported asynchronously by the OS, so allow an amount of time for // them to arrive before testing an assertion. If we expect an event to occur, @@ -23,18 +23,20 @@ namespace System.IO.Tests public const int LongWaitTimeout = 50000; // ms to wait for an event that takes a longer time than the average operation public const int SubsequentExpectedWait = 10; // ms to wait for checks that occur after the first. public const int WaitForExpectedEventTimeout_NoRetry = 3000;// ms to wait for an event that isn't surrounded by a retry. + public const int WaitForUnexpectedEventTimeout = 150; // ms to wait for a non-expected event. public const int DefaultAttemptsForExpectedEvent = 3; // Number of times an expected event should be retried if failing. public const int DefaultAttemptsForUnExpectedEvent = 2; // Number of times an unexpected event should be retried if failing. + public const int RetryDelayMilliseconds = 500; // ms to wait when retrying after failure /// /// Watches the Changed WatcherChangeType and unblocks the returned AutoResetEvent when a /// Changed event is thrown by the watcher. /// - public static AutoResetEvent WatchChanged(FileSystemWatcher watcher, string[] expectedPaths = null) + public static (AutoResetEvent EventOccured, FileSystemEventHandler Handler) WatchChanged(FileSystemWatcher watcher, string[] expectedPaths = null) { AutoResetEvent eventOccurred = new AutoResetEvent(false); - watcher.Changed += (o, e) => + FileSystemEventHandler changeHandler = (o, e) => { Assert.Equal(WatcherChangeTypes.Changed, e.ChangeType); if (expectedPaths != null) @@ -44,18 +46,19 @@ namespace System.IO.Tests eventOccurred.Set(); }; - return eventOccurred; + watcher.Changed += changeHandler; + return (eventOccurred, changeHandler); } /// /// Watches the Created WatcherChangeType and unblocks the returned AutoResetEvent when a /// Created event is thrown by the watcher. /// - public static AutoResetEvent WatchCreated(FileSystemWatcher watcher, string[] expectedPaths = null) + public static (AutoResetEvent EventOccured, FileSystemEventHandler Handler) WatchCreated(FileSystemWatcher watcher, string[] expectedPaths = null) { AutoResetEvent eventOccurred = new AutoResetEvent(false); - watcher.Created += (o, e) => + FileSystemEventHandler handler = (o, e) => { Assert.Equal(WatcherChangeTypes.Created, e.ChangeType); if (expectedPaths != null) @@ -65,18 +68,18 @@ namespace System.IO.Tests eventOccurred.Set(); }; - return eventOccurred; + watcher.Created += handler; + return (eventOccurred, handler); } /// /// Watches the Renamed WatcherChangeType and unblocks the returned AutoResetEvent when a /// Renamed event is thrown by the watcher. /// - public static AutoResetEvent WatchDeleted(FileSystemWatcher watcher, string[] expectedPaths = null) + public static (AutoResetEvent EventOccured, FileSystemEventHandler Handler) WatchDeleted(FileSystemWatcher watcher, string[] expectedPaths = null) { AutoResetEvent eventOccurred = new AutoResetEvent(false); - - watcher.Deleted += (o, e) => + FileSystemEventHandler handler = (o, e) => { Assert.Equal(WatcherChangeTypes.Deleted, e.ChangeType); if (expectedPaths != null) @@ -86,18 +89,19 @@ namespace System.IO.Tests eventOccurred.Set(); }; - return eventOccurred; + watcher.Deleted += handler; + return (eventOccurred, handler); } /// /// Watches the Renamed WatcherChangeType and unblocks the returned AutoResetEvent when a /// Renamed event is thrown by the watcher. /// - public static AutoResetEvent WatchRenamed(FileSystemWatcher watcher, string[] expectedPaths = null) + public static (AutoResetEvent EventOccured, RenamedEventHandler Handler) WatchRenamed(FileSystemWatcher watcher, string[] expectedPaths = null) { AutoResetEvent eventOccurred = new AutoResetEvent(false); - watcher.Renamed += (o, e) => + RenamedEventHandler handler = (o, e) => { Assert.Equal(WatcherChangeTypes.Renamed, e.ChangeType); if (expectedPaths != null) @@ -107,7 +111,8 @@ namespace System.IO.Tests eventOccurred.Set(); }; - return eventOccurred; + watcher.Renamed += handler; + return (eventOccurred, handler); } /// @@ -115,7 +120,7 @@ namespace System.IO.Tests /// public static void ExpectEvent(WaitHandle eventOccurred, string eventName_NoRetry) { - string message = String.Format("Didn't observe a {0} event within {1}ms", eventName_NoRetry, WaitForExpectedEventTimeout_NoRetry); + string message = string.Format("Didn't observe a {0} event within {1}ms", eventName_NoRetry, WaitForExpectedEventTimeout_NoRetry); Assert.True(eventOccurred.WaitOne(WaitForExpectedEventTimeout_NoRetry), message); } @@ -161,32 +166,63 @@ namespace System.IO.Tests { int attemptsCompleted = 0; bool result = false; + FileSystemWatcher newWatcher = watcher; while (!result && attemptsCompleted++ < attempts) { if (attemptsCompleted > 1) { // Re-create the watcher to get a clean iteration. - watcher = new FileSystemWatcher() - { - IncludeSubdirectories = watcher.IncludeSubdirectories, - NotifyFilter = watcher.NotifyFilter, - Filter = watcher.Filter, - Path = watcher.Path, - InternalBufferSize = watcher.InternalBufferSize - }; + newWatcher = RecreateWatcher(newWatcher); // Most intermittent failures in FSW are caused by either a shortage of resources (e.g. inotify instances) // or by insufficient time to execute (e.g. CI gets bogged down). Immediately re-running a failed test // won't resolve the first issue, so we wait a little while hoping that things clear up for the next run. - Thread.Sleep(500); + Thread.Sleep(RetryDelayMilliseconds); } - result = ExecuteAndVerifyEvents(watcher, expectedEvents, action, attemptsCompleted == attempts, expectedPaths, timeout); + result = ExecuteAndVerifyEvents(newWatcher, expectedEvents, action, attemptsCompleted == attempts, expectedPaths, timeout); if (cleanup != null) cleanup(); } } + /// Invokes the specified test action with retry on failure (other than assertion failure). + /// The test action. + /// The maximum number of times to attempt to run the test. + public static void ExecuteWithRetry(Action action, int maxAttempts = DefaultAttemptsForExpectedEvent) + { + for (int retry = 0; retry < maxAttempts; retry++) + { + try + { + action(); + return; + } + catch (Exception e) when (!(e is XunitException) && retry < maxAttempts - 1) + { + Thread.Sleep(RetryDelayMilliseconds); + } + } + } + + /// + /// Does verification that the given watcher will not throw exactly/only the events in "expectedEvents" when + /// "action" is executed. + /// + /// The FileSystemWatcher to test + /// All of the events that are expected to be raised by this action + /// The Action that will trigger events. + /// Optional. Undoes the action and cleans up the watcher so the test may be run again if necessary. + /// Optional. Adds path verification to all expected events. + public static void ExpectNoEvent(FileSystemWatcher watcher, WatcherChangeTypes unExpectedEvents, Action action, Action cleanup = null, string expectedPath = null, int timeout = WaitForExpectedEventTimeout) + { + bool result = ExecuteAndVerifyEvents(watcher, unExpectedEvents, action, false, new string[] { expectedPath }, timeout); + Assert.False(result, "Expected Event occured"); + + if (cleanup != null) + cleanup(); + } + /// /// Helper for the ExpectEvent function. /// @@ -199,8 +235,8 @@ namespace System.IO.Tests public static bool ExecuteAndVerifyEvents(FileSystemWatcher watcher, WatcherChangeTypes expectedEvents, Action action, bool assertExpected, string[] expectedPaths, int timeout) { bool result = true, verifyChanged = true, verifyCreated = true, verifyDeleted = true, verifyRenamed = true; - AutoResetEvent changed = null, created = null, deleted = null, renamed = null; - string[] expectedFullPaths = expectedPaths == null ? null : expectedPaths.Select(e => Path.GetFullPath(e)).ToArray(); + (AutoResetEvent EventOccured, FileSystemEventHandler Handler) changed = default, created = default, deleted = default; + (AutoResetEvent EventOccured, RenamedEventHandler Handler) renamed = default; if (verifyChanged = ((expectedEvents & WatcherChangeTypes.Changed) > 0)) changed = WatchChanged(watcher, expectedPaths); @@ -218,7 +254,8 @@ namespace System.IO.Tests if (verifyChanged) { bool Changed_expected = ((expectedEvents & WatcherChangeTypes.Changed) > 0); - bool Changed_actual = changed.WaitOne(timeout); + bool Changed_actual = changed.EventOccured.WaitOne(timeout); + watcher.Changed -= changed.Handler; result = Changed_expected == Changed_actual; if (assertExpected) Assert.True(Changed_expected == Changed_actual, "Changed event did not occur as expected"); @@ -228,7 +265,8 @@ namespace System.IO.Tests if (verifyCreated) { bool Created_expected = ((expectedEvents & WatcherChangeTypes.Created) > 0); - bool Created_actual = created.WaitOne(verifyChanged ? SubsequentExpectedWait : timeout); + bool Created_actual = created.EventOccured.WaitOne(verifyChanged ? SubsequentExpectedWait : timeout); + watcher.Created -= created.Handler; result = result && Created_expected == Created_actual; if (assertExpected) Assert.True(Created_expected == Created_actual, "Created event did not occur as expected"); @@ -238,7 +276,8 @@ namespace System.IO.Tests if (verifyDeleted) { bool Deleted_expected = ((expectedEvents & WatcherChangeTypes.Deleted) > 0); - bool Deleted_actual = deleted.WaitOne(verifyChanged || verifyCreated ? SubsequentExpectedWait : timeout); + bool Deleted_actual = deleted.EventOccured.WaitOne(verifyChanged || verifyCreated ? SubsequentExpectedWait : timeout); + watcher.Deleted -= deleted.Handler; result = result && Deleted_expected == Deleted_actual; if (assertExpected) Assert.True(Deleted_expected == Deleted_actual, "Deleted event did not occur as expected"); @@ -248,7 +287,8 @@ namespace System.IO.Tests if (verifyRenamed) { bool Renamed_expected = ((expectedEvents & WatcherChangeTypes.Renamed) > 0); - bool Renamed_actual = renamed.WaitOne(verifyChanged || verifyCreated || verifyDeleted? SubsequentExpectedWait : timeout); + bool Renamed_actual = renamed.EventOccured.WaitOne(verifyChanged || verifyCreated || verifyDeleted ? SubsequentExpectedWait : timeout); + watcher.Renamed -= renamed.Handler; result = result && Renamed_expected == Renamed_actual; if (assertExpected) Assert.True(Renamed_expected == Renamed_actual, "Renamed event did not occur as expected"); @@ -409,7 +449,7 @@ namespace System.IO.Tests foreach (NotifyFilters filter in Enum.GetValues(typeof(NotifyFilters))) yield return new object[] { filter }; } - + // Linux and OSX systems have less precise filtering systems than Windows, so most // metadata filters are effectively equivalent to each other on those systems. For example // there isn't a way to filter only LastWrite events on either system; setting @@ -430,5 +470,19 @@ namespace System.IO.Tests NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Size; + +#if MONO + private static FileSystemWatcher RecreateWatcher(FileSystemWatcher watcher) + { + FileSystemWatcher newWatcher = new FileSystemWatcher() + { + IncludeSubdirectories = watcher.IncludeSubdirectories, + NotifyFilter = watcher.NotifyFilter, + Path = watcher.Path, + InternalBufferSize = watcher.InternalBufferSize + }; + return newWatcher; + } +#endif } -} +} \ No newline at end of file diff --git a/external/corefx/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs b/external/corefx/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs index d272734f96..8dff0e23ae 100644 --- a/external/corefx/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs +++ b/external/corefx/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs @@ -397,27 +397,31 @@ namespace System.IO } } - public static bool DirectoryExists(string fullPath) + public static bool DirectoryExists(ReadOnlySpan fullPath) { Interop.ErrorInfo ignored; return DirectoryExists(fullPath, out ignored); } - private static bool DirectoryExists(string fullPath, out Interop.ErrorInfo errorInfo) + private static bool DirectoryExists(ReadOnlySpan fullPath, out Interop.ErrorInfo errorInfo) { return FileExists(fullPath, Interop.Sys.FileTypes.S_IFDIR, out errorInfo); } - public static bool FileExists(string fullPath) + public static bool FileExists(ReadOnlySpan fullPath) { - Interop.ErrorInfo ignored; - - // Input allows trailing separators in order to match Windows behavior - // Unix does not accept trailing separators, so must be trimmed + Interop.ErrorInfo ignored; + // File.Exists() explicitly checks for a trailing separator and returns false if found. FileInfo.Exists and all other + // internal usages do not check for the trailing separator. Historically we've always removed the trailing separator + // when getting attributes as trailing separators are generally not accepted by Windows APIs. Unix will take + // trailing separators, but it infers that the path must be a directory (it effectively appends "."). To align with + // our historical behavior (outside of File.Exists()), we need to trim. + // + // See http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11 for details. return FileExists(PathInternal.TrimEndingDirectorySeparator(fullPath), Interop.Sys.FileTypes.S_IFREG, out ignored); } - private static bool FileExists(string fullPath, int fileType, out Interop.ErrorInfo errorInfo) + private static bool FileExists(ReadOnlySpan fullPath, int fileType, out Interop.ErrorInfo errorInfo) { Debug.Assert(fileType == Interop.Sys.FileTypes.S_IFREG || fileType == Interop.Sys.FileTypes.S_IFDIR); diff --git a/mcs/build/common/Consts.cs b/mcs/build/common/Consts.cs index 388cd4807c..31eb46f02c 100644 --- a/mcs/build/common/Consts.cs +++ b/mcs/build/common/Consts.cs @@ -34,7 +34,7 @@ static class Consts // Use these assembly version constants to make code more maintainable. // - public const string MonoVersion = "5.18.0.237"; + public const string MonoVersion = "5.18.0.239"; public const string MonoCompany = "Mono development team"; public const string MonoProduct = "Mono Common Language Infrastructure"; public const string MonoCopyright = "(c) Various Mono authors"; diff --git a/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/Mono.Security.dll.REMOVED.git-id b/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/Mono.Security.dll.REMOVED.git-id index aa5858eead..d28bde5512 100644 --- a/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/Mono.Security.dll.REMOVED.git-id +++ b/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/Mono.Security.dll.REMOVED.git-id @@ -1 +1 @@ -908cd04819fde0fa5b49a8179b597c3a66e22c4e \ No newline at end of file +6906969e1560c229aa270d601a88da36d3b87383 \ No newline at end of file diff --git a/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/System.Configuration.dll.REMOVED.git-id b/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/System.Configuration.dll.REMOVED.git-id index 742b2c4e84..9cccaca5d8 100644 --- a/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/System.Configuration.dll.REMOVED.git-id +++ b/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/System.Configuration.dll.REMOVED.git-id @@ -1 +1 @@ -7cfb76b39793a684c9a17a0405973089bcd5f954 \ No newline at end of file +8260010ca66cf7bf43ddbb1ae0b222492747167a \ No newline at end of file diff --git a/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/System.Core.dll.REMOVED.git-id b/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/System.Core.dll.REMOVED.git-id index 0dc90e9181..3d1691dcac 100644 --- a/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/System.Core.dll.REMOVED.git-id +++ b/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/System.Core.dll.REMOVED.git-id @@ -1 +1 @@ -c95ba4d04ef7f337af46d383ede8538bd5035b34 \ No newline at end of file +2d5243eb818f2ae2bd193faffdc1cd4ea4700dd7 \ No newline at end of file diff --git a/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/System.IO.Compression.dll b/mcs/class/lib/monolite-linux/CA4932AE-2294-4ECD-B863-BF98FDD84F33/System.IO.Compression.dll index cbff9ac556fa3ec6de661e773497e276dec82d8d..14e12d232df9c3d64578be65d85922924317979d 100644 GIT binary patch delta 58 zcmZqZU~A}Lo6x~>G%U8Qv1e-!qX~~dS=pako_+rAJ15sJ3+?I^+kSzEF^i4al0j?w Of?7tc?digdGYbH_ffjlI delta 58 zcmV-A0LA}+hz5X&29SsZTcf+hfsD0`0W=H{;K|3;bOmQhv$Ys@iRcH>x6lj$Y6b&0 Q04kTDeE}-BXA=RLaNG%U8Qv1e-!qX~~dS=pako_+rAJ15sJ3+?I^+kSzEF^i4al0j?w Of?7tc?digdGYbH_ffjlI delta 58 zcmV-A0LA}+hz5X&29SsZTcf+hfsD0`0W=H{;K|3;bOmQhv$Ys@iRcH>x6lj$Y6b&0 Q04kTDeE}-BXA=RLaNG%U8Qv1e-!qX~~dS=pako_+rAJ15sJ3+?I^+kSzEF^i4al0j?w Of?7tc?digdGYbH_ffjlI delta 58 zcmV-A0LA}+hz5X&29SsZTcf+hfsD0`0W=H{;K|3;bOmQhv$Ys@iRcH>x6lj$Y6b&0 Q04kTDeE}-BXA=RLaNG%U8Qv1e-!qX~~dS=pako_+rAJ15sJ3+?I^+kSzEF^i4al0j?w Of?7tc?digdGYbH_ffjlI delta 58 zcmV-A0LA}+hz5X&29SsZTcf+hfsD0`0W=H{;K|3;bOmQhv$Ys@iRcH>x6lj$Y6b&0 Q04kTDeE}-BXA=RLaN1p^B!1Ix{CxQw^}N~Z>V delta 20 bcmbQIHBW29Q!aKR1p^B!1GCL_!R(7FGskoB7oXv;bh^2M7QF diff --git a/po/mcs/ja.po.REMOVED.git-id b/po/mcs/ja.po.REMOVED.git-id index 98de4e7053..08b6d75313 100644 --- a/po/mcs/ja.po.REMOVED.git-id +++ b/po/mcs/ja.po.REMOVED.git-id @@ -1 +1 @@ -b4fb7a778bf8cea7d5aed1673c98ff39b4fd8468 \ No newline at end of file +3a581243027655a91f7ea8149e2cd44124293f47 \ No newline at end of file diff --git a/po/mcs/mcs.pot b/po/mcs/mcs.pot index 17597b71a0..cdd4293298 100644 --- a/po/mcs/mcs.pot +++ b/po/mcs/mcs.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: mono 5.18.0.237\n" +"Project-Id-Version: mono 5.18.0.239\n" "Report-Msgid-Bugs-To: http://www.mono-project.com/Bugs\n" -"POT-Creation-Date: 2019-01-12 08:06+0000\n" +"POT-Creation-Date: 2019-01-15 08:09+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/po/mcs/pt_BR.gmo b/po/mcs/pt_BR.gmo index 5215a163ec1340944e0849038dcd97b9403a6222..d7112158cfc4c3073a9581f265b81063b6b0ec11 100644 GIT binary patch delta 22 ecmaF1gXP%{mJM-J*-aG;EUXMHH>XZzYzF{t5D0_- delta 22 ecmaF1gXP%{mJM-J*^LwoEUXO7Hm6QyYzF{s+6aCC diff --git a/po/mcs/pt_BR.po.REMOVED.git-id b/po/mcs/pt_BR.po.REMOVED.git-id index 9da0840167..1fd265b345 100644 --- a/po/mcs/pt_BR.po.REMOVED.git-id +++ b/po/mcs/pt_BR.po.REMOVED.git-id @@ -1 +1 @@ -f62a6a2e821d7772818d78785a49b72df9f2a7a5 \ No newline at end of file +a626d442d10692d9e6487fe822577c949347180b \ No newline at end of file