[StructLayout(LayoutKind.Sequential)]
struct timespec {
public IntPtr tv_sec;
- public IntPtr tv_usec;
+ public IntPtr tv_nsec;
}
class PathData
class KqueueMonitor : IDisposable
{
+ static bool initialized;
+
public int Connection
{
get { return conn; }
{
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 ()
startedEvent.WaitOne ();
- if (monitorExc != null) {
+ if (exc != null) {
thread.Join ();
CleanUp ();
- throw monitorExc;
+ throw exc;
}
- else
- started = true;
+
+ started = true;
}
}
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);
}
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;
}
}
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;
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);
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> ();
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;
}
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;
}
return pathData;
} catch (Exception e) {
close (fd);
- fsw.OnError (new ErrorEventArgs (e));
+ fsw.DispatchErrorEvents (new ErrorEventArgs (e));
return null;
}
{
RenamedEventArgs renamed = null;
- if (action == 0)
+ if (requestStop || action == 0)
return;
// e.Name
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);
}
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;
}
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;
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