Merge pull request #2831 from razzfazz/fix_dllimport
[mono.git] / mcs / class / System / System.IO / KeventWatcher.cs
index 09db0b238968471309aa35f762d95eadcefdfacb..8078964da30ea9f5e252b24e710e5131ea7de5b5 100644 (file)
@@ -150,7 +150,7 @@ namespace System.IO {
        [StructLayout(LayoutKind.Sequential)]
        struct timespec {
                public IntPtr tv_sec;
-               public IntPtr tv_usec;
+               public IntPtr tv_nsec;
        }
 
        class PathData
@@ -162,6 +162,8 @@ namespace System.IO {
 
        class KqueueMonitor : IDisposable
        {
+               static bool initialized;
+               
                public int Connection
                {
                        get { return conn; }
@@ -171,6 +173,13 @@ namespace System.IO {
                {
                        this.fsw = fsw;
                        this.conn = -1;
+                       if (!initialized){
+                               int t;
+                               initialized = true;
+                               var maxenv = Environment.GetEnvironmentVariable ("MONO_DARWIN_WATCHER_MAXFDS");
+                               if (maxenv != null && Int32.TryParse (maxenv, out t))
+                                       maxFds = t;
+                       }
                }
 
                public void Dispose ()
@@ -196,13 +205,13 @@ namespace System.IO {
 
                                startedEvent.WaitOne ();
 
-                               if (monitorExc != null) {
+                               if (exc != null) {
                                        thread.Join ();
                                        CleanUp ();
-                                       throw monitorExc;
+                                       throw exc;
                                }
-                               else 
-                                       started = true;
+                               started = true;
                        }
                }
 
@@ -213,23 +222,34 @@ namespace System.IO {
                                        return;
                                        
                                requestStop = true;
-                               thread.Join ();
-                               requestStop = false;
 
-                               CleanUp ();
+                               if (inDispatch)
+                                       return;
+                               // This will break the wait in Monitor ()
+                               lock (connLock) {
+                                       if (conn != -1)
+                                               close (conn);
+                                       conn = -1;
+                               }
+
+                               if (!thread.Join (2000))
+                                       thread.Abort ();
+
+                               requestStop = false;
                                started = false;
 
-                               if (monitorExc != null)
-                                       throw monitorExc;
+                               if (exc != null)
+                                       throw exc;
                        }
                }
 
                void CleanUp ()
                {
-                       if (conn != -1)
-                               close (conn);
-
-                       conn = -1;
+                       lock (connLock) {
+                               if (conn != -1)
+                                       close (conn);
+                               conn = -1;
+                       }
 
                        foreach (int fd in fdsDict.Keys)
                                close (fd); 
@@ -239,32 +259,34 @@ namespace System.IO {
                }
 
                void DoMonitor ()
-               {
-                       monitorExc = null;
-
+               {                       
                        try {
                                Setup ();
                        } catch (Exception e) {
-                               monitorExc = e;
+                               exc = e;
                        } finally {
                                startedEvent.Set ();
                        }
 
-                       if (monitorExc != null) 
+                       if (exc != null) {
+                               fsw.DispatchErrorEvents (new ErrorEventArgs (exc));
                                return;
+                       }
 
                        try {
                                Monitor ();
                        } catch (Exception e) {
-                               monitorExc = e;
+                               exc = e;
                        } finally {
+                               CleanUp ();
                                if (!requestStop) { // failure
-                                       CleanUp ();
                                        started = false;
-                                       if (monitorExc != null)
-                                               throw monitorExc;
+                                       inDispatch = false;
+                                       fsw.EnableRaisingEvents = false;
                                }
-
+                               if (exc != null)
+                                       fsw.DispatchErrorEvents (new ErrorEventArgs (exc));
+                               requestStop = false;
                        }
                }
 
@@ -273,7 +295,7 @@ namespace System.IO {
                        var initialFds = new List<int> ();
 
                        // fsw.FullPath may end in '/', see https://bugzilla.xamarin.com/show_bug.cgi?id=5747
-                       if (fsw.FullPath.EndsWith ("/", StringComparison.Ordinal))
+                       if (fsw.FullPath != "/" && fsw.FullPath.EndsWith ("/", StringComparison.Ordinal))
                                fullPathNoLastSlash = fsw.FullPath.Substring (0, fsw.FullPath.Length - 1);
                        else
                                fullPathNoLastSlash = fsw.FullPath;
@@ -291,7 +313,7 @@ namespace System.IO {
 
                        Scan (fullPathNoLastSlash, false, ref initialFds);
 
-                       var immediate_timeout = new timespec { tv_sec = (IntPtr)0, tv_usec = (IntPtr)0 };
+                       var immediate_timeout = new timespec { tv_sec = (IntPtr)0, tv_nsec = (IntPtr)0 };
                        var eventBuffer = new kevent[0]; // we don't want to take any events from the queue at this point
                        var changes = CreateChangeList (ref initialFds);
 
@@ -332,7 +354,6 @@ namespace System.IO {
 
                void Monitor ()
                {
-                       var timeout = new timespec { tv_sec = (IntPtr)0, tv_usec = (IntPtr)500000000 };
                        var eventBuffer = new kevent[32];
                        var newFds = new List<int> ();
                        List<PathData> removeQueue = new List<PathData> ();
@@ -343,29 +364,54 @@ namespace System.IO {
                        while (!requestStop) {
                                var changes = CreateChangeList (ref newFds);
 
-                               int numEvents = kevent (conn, changes, changes.Length, eventBuffer, eventBuffer.Length, ref timeout);
+                               // We are calling an icall, so have to marshal manually
+                               // Marshal in
+                               int ksize = Marshal.SizeOf<kevent> ();
+                               var changesNative = Marshal.AllocHGlobal (ksize * changes.Length);
+                               for (int i = 0; i < changes.Length; ++i)
+                                       Marshal.StructureToPtr (changes [i], changesNative + (i * ksize), false);
+                               var eventBufferNative = Marshal.AllocHGlobal (ksize * eventBuffer.Length);
+
+                               int numEvents = kevent_notimeout (ref conn, changesNative, changes.Length, eventBufferNative, eventBuffer.Length);
+
+                               // Marshal out
+                               Marshal.FreeHGlobal (changesNative);
+                               for (int i = 0; i < numEvents; ++i)
+                                       eventBuffer [i] = Marshal.PtrToStructure<kevent> (eventBufferNative + (i * ksize));
+                               Marshal.FreeHGlobal (eventBufferNative);
 
                                if (numEvents == -1) {
+                                       // Stop () signals us to stop by closing the connection
+                                       if (requestStop)
+                                               break;
                                        if (++retries == 3)
                                                throw new IOException (String.Format (
                                                        "persistent kevent() error, error code = '{0}'", Marshal.GetLastWin32Error ()));
 
                                        continue;
                                }
-
                                retries = 0;
 
                                for (var i = 0; i < numEvents; i++) {
                                        var kevt = eventBuffer [i];
+
+                                       if (!fdsDict.ContainsKey ((int)kevt.ident))
+                                               // The event is for a file that was removed
+                                               continue;
+
                                        var pathData = fdsDict [(int)kevt.ident];
 
                                        if ((kevt.flags & EventFlags.Error) == EventFlags.Error) {
                                                var errMsg = String.Format ("kevent() error watching path '{0}', error code = '{1}'", pathData.Path, kevt.data);
-                                               fsw.OnError (new ErrorEventArgs (new IOException (errMsg)));
+                                               fsw.DispatchErrorEvents (new ErrorEventArgs (new IOException (errMsg)));
                                                continue;
                                        }
                                                
                                        if ((kevt.fflags & FilterFlags.VNodeDelete) == FilterFlags.VNodeDelete || (kevt.fflags & FilterFlags.VNodeRevoke) == FilterFlags.VNodeRevoke) {
+                                               if (pathData.Path == fullPathNoLastSlash)
+                                                       // The root path is deleted; exit silently
+                                                       return;
+                                                               
                                                removeQueue.Add (pathData);
                                                continue;
                                        }
@@ -404,12 +450,12 @@ namespace System.IO {
                                return pathData;
 
                        if (fdsDict.Count >= maxFds)
-                               throw new IOException ("kqueue() FileSystemWatcher has reached the maximum nunmber of files to watch."); 
+                               throw new IOException ("kqueue() FileSystemWatcher has reached the maximum number of files to watch."); 
 
                        var fd = open (path, O_EVTONLY, 0);
 
                        if (fd == -1) {
-                               fsw.OnError (new ErrorEventArgs (new IOException (String.Format (
+                               fsw.DispatchErrorEvents (new ErrorEventArgs (new IOException (String.Format (
                                        "open() error while attempting to process path '{0}', error code = '{1}'", path, Marshal.GetLastWin32Error ()))));
                                return null;
                        }
@@ -434,7 +480,7 @@ namespace System.IO {
                                return pathData;
                        } catch (Exception e) {
                                close (fd);
-                               fsw.OnError (new ErrorEventArgs (e));
+                               fsw.DispatchErrorEvents (new ErrorEventArgs (e));
                                return null;
                        }
 
@@ -556,7 +602,7 @@ namespace System.IO {
                {
                        RenamedEventArgs renamed = null;
 
-                       if (action == 0)
+                       if (requestStop || action == 0)
                                return;
 
                        // e.Name
@@ -570,11 +616,11 @@ namespace System.IO {
                                string newName = newPath.Substring (fullPathNoLastSlash.Length + 1);
                                renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, fsw.Path, newName, name);
                        }
+                               
+                       fsw.DispatchEvents (action, name, ref renamed);
 
-                       lock (fsw) {
-                               fsw.DispatchEvents (action, name, ref renamed);
-
-                               if (fsw.Waiting) {
+                       if (fsw.Waiting) {
+                               lock (fsw) {
                                        fsw.Waiting = false;
                                        System.Threading.Monitor.PulseAll (fsw);
                                }
@@ -591,7 +637,7 @@ namespace System.IO {
 
                                return sb.ToString ();
                        } else {
-                               fsw.OnError (new ErrorEventArgs (new IOException (String.Format (
+                               fsw.DispatchErrorEvents (new ErrorEventArgs (new IOException (String.Format (
                                        "fcntl() error while attempting to get path for fd '{0}', error code = '{1}'", fd, Marshal.GetLastWin32Error ()))));
                                return String.Empty;
                        }
@@ -601,7 +647,7 @@ namespace System.IO {
                const int F_GETPATH = 50;
                const int __DARWIN_MAXPATHLEN = 1024;
                static readonly kevent[] emptyEventList = new System.IO.kevent[0];
-               const int maxFds = 200;
+               int maxFds = Int32.MaxValue;
 
                FileSystemWatcher fsw;
                int conn;
@@ -609,28 +655,33 @@ namespace System.IO {
                volatile bool requestStop = false;
                AutoResetEvent startedEvent = new AutoResetEvent (false);
                bool started = false;
-               Exception monitorExc;
+               bool inDispatch = false;
+               Exception exc = null;
                object stateLock = new object ();
+               object connLock = new object ();
 
                readonly Dictionary<string, PathData> pathsDict = new Dictionary<string, PathData> ();
                readonly Dictionary<int, PathData> fdsDict = new Dictionary<int, PathData> ();
                string fixupPath = null;
                string fullPathNoLastSlash = null;
 
-               [DllImport ("libc", EntryPoint="fcntl", CharSet=CharSet.Auto, SetLastError=true)]
+               [DllImport ("libc", CharSet=CharSet.Auto, SetLastError=true)]
                static extern int fcntl (int file_names_by_descriptor, int cmd, StringBuilder sb);
 
-               [DllImport ("libc")]
+               [DllImport ("libc", SetLastError=true)]
                extern static int open (string path, int flags, int mode_t);
 
                [DllImport ("libc")]
                extern static int close (int fd);
 
-               [DllImport ("libc")]
+               [DllImport ("libc", SetLastError=true)]
                extern static int kqueue ();
 
-               [DllImport ("libc")]
+               [DllImport ("libc", SetLastError=true)]
                extern static int kevent (int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, [In] ref timespec time);
+
+               [MethodImplAttribute(MethodImplOptions.InternalCall)]
+               extern static int kevent_notimeout (ref int kq, IntPtr ev, int nchanges, IntPtr evtlist, int nevents);
        }
 
        class KeventWatcher : IFileWatcher