Update: added sample code to detect the watcher in use, courtesy of Robert Jordan - thanks!
mod_mono is an Apache module for hosting ASP.NET applications. The
module itself doesn't run any .NET code, instead it spawns a backend server (mod-mono-server.exe for ASP.NET 1.1 and
mod-mono-server2.exe for ASP.NET 2.0) which is handed all the requests coming in from the client browser and sends back
response generated by the application.
If you run Mono on a VPS server (e.g. Xen, OpenVZ) then you don't usually have any control over what Linux kernel version
and with what capabilities you run. It may happen that the kernel lacks capabilities used by parts of the Mono runtime and Mono
will have to fall back to other methods of doing the same task. One such part is the
FileSystemWatcher class
which is used to monitor changes to files/directories on disk so that the application can take any steps it deems necessary in
reaction to file creation/deletion/modification events.
Mono's FileSystemWatcher does its best to perform its assigned task in various environments, under various operating systems.
Part of the effort is selecting the actual filesystem monitoring backend best for the runtime environment. Under Unix the supported backends are as follow:
- FAM
- kevent (BSD*/MacOSX only)
- gamin
- inotify (Linux only)
- Managed watcher
Out of those, assuming you run Linux,
inotify is the preferred backend mechanism as it requires no polling effort on
userland application part, instead the Linux kernel will notify the application (in our case the Mono runtime) whenever interesting events
happen. However, it requires the Linux kernel to support the mechanism and, what's more important, for your VPS operator to actually include
the support in the kernel your VPS runs on.
If your kernel doesn't support inotify, Mono will attempt to use FAM and Gamin which are userland daemons doing active filesystem polling but
outside of the consumer application. The consumer application will use provided FAM/Gamin libraries to receive events and react
to them. Performance of this setup is worse than inotify but not tragic.
Should Mono fail to detect inotify, FAM or Gamin support, it will fall back to the last resort option - the managed watcher.
This watcher is implemented in managed code and uses a separate thread for filesystem monitoring, polling for changes on selected files/directories.
As the application may (and in the case of ASP.NET sometimes does) watch directories recursively, it might be a very expensive situation requiring
checking changes to a big set of files. Each change detection run requires checking whether a file/directory exists (in case of the Managed watcher
those are two stat (2) calls) and then checking the file metadata for changes (size, modification times etc) and, possibly, generating an event.
This happens approximately every 750ms and can generate substantial load on the server's CPU.
If you notice (using top or htop applications) that your copy of mod-mono-server burns several per-cent of CPU but is otherwise in the S (Sleeping)
process state, chances are your application is using the managed watcher. You can confirm that by using htop which allows you to watch individual process
threads - you will see two threads consuming nearly the same amount of CPU time and one of them waking up every ~750ms.
The cure for the itch is easy, if you can live without filesystem monitoring (that means your application will not auto-restart when you modify Web.config,
files won't be recompiled if you modify a code-behind .cs or an .aspx, .ascx etc. files). Mono supports a MONO_MANAGED_WATCHER environment
variable which can be set to value disable with the effect of definitely disabling filesystem monitoring (it will use a "dumb" implementation of the
watcher backend which does nothing) and relieve your application of the filesystem polling chores described above.
You can set the environment variable for your Apache VirtualHost by using the following mod_mono directive:
MonoSetEnv [server_alias] MONO_MANAGED_WATCHER=disable
Sample program to detect which watcher backend is used:
using System;
using System.Reflection;
using System.IO;
class Program {
public static void Main()
{
object watcher = new FileSystemWatcher()
.GetType ()
.GetField ("watcher", BindingFlags.NonPublic | BindingFlags.Static)
.GetValue (null);
Console.WriteLine (watcher != null
? watcher.GetType ().FullName
: "unknown");
}
}