Merging from head
[beagle.git] / beagled / BeagleDaemon.cs
blobe1933f17981b3b3d323ffae7c79f8373204e1bee
1 //
2 // BeagleDaemon.cs
3 //
4 // Copyright (C) 2004-2006 Novell, Inc.
5 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
27 using System;
28 using System.Collections;
29 using System.Diagnostics;
30 using System.IO;
31 using System.Reflection;
32 using System.Runtime.InteropServices;
33 using System.Threading;
34 using Thread = System.Threading.Thread;
35 using GLib;
37 using Beagle.Util;
38 using Log = Beagle.Util.Log;
40 namespace Beagle.Daemon {
41 class BeagleDaemon {
43 public static Thread MainLoopThread = null;
44 private static MainLoop main_loop = null;
46 private static Server server = null;
48 private static bool arg_replace = false;
49 private static bool arg_disable_scheduler = false;
50 private static bool arg_indexing_test_mode = false;
51 private static bool arg_heap_shot = false;
53 public static bool StartServer ()
55 Logger.Log.Debug ("Starting messaging server");
57 try {
58 server = new Server ("socket");
59 server.Start ();
60 } catch (InvalidOperationException) {
61 return false;
64 return true;
67 public static void ReplaceExisting ()
69 Logger.Log.Info ("Attempting to replace another beagled.");
71 do {
72 ShutdownRequest request = new ShutdownRequest ();
73 Logger.Log.Info ("Sending Shutdown");
74 request.Send ();
75 // Give it a second to shut down the messaging server
76 Thread.Sleep (1000);
77 } while (! StartServer ());
80 private static int prev_rss = -1;
81 private static long prev_gc = -1;
82 private static int sigprof_count = 0;
84 private static void MaybeSendSigprof (int rss, long gc)
86 bool send_sigprof = false;
88 try {
89 if (prev_rss == -1 || prev_gc == -1)
90 return;
92 // Log RSS increases of at least 5 megs or 5%
93 if (rss - prev_rss >= 5 * 1024 ||
94 (double) rss / (double) prev_rss >= 1.05)
95 send_sigprof = true;
97 // Log total object size increase of at least 10%.
98 if ((double) gc / (double) prev_gc >= 1.1)
99 send_sigprof = true;
100 } finally {
101 prev_rss = rss;
102 prev_gc = gc;
104 if (send_sigprof) {
105 Log.Debug ("Suspicious memory size change detected. Sending SIGPROF to ourself ({0})", sigprof_count++);
106 Mono.Unix.Native.Syscall.kill (Process.GetCurrentProcess ().Id, Mono.Unix.Native.Signum.SIGPROF);
111 private static void LogMemoryUsage ()
113 while (! Shutdown.ShutdownRequested) {
114 SystemInformation.LogMemoryUsage ();
116 int vm_rss = SystemInformation.VmRss;
118 if (arg_heap_shot)
119 MaybeSendSigprof (vm_rss, GC.GetTotalMemory (false));
121 if (vm_rss > 300 * 1024) {
122 Logger.Log.Debug ("VmRss too large --- shutting down");
123 Shutdown.BeginShutdown ();
126 Thread.Sleep (5000);
130 private static void PrintUsage ()
132 string usage =
133 "beagled: The daemon to the Beagle search system.\n" +
134 "Web page: http://beagle-project.org\n" +
135 "Copyright (C) 2004-2006 Novell, Inc.\n\n";
137 usage +=
138 "Usage: beagled [OPTIONS]\n\n" +
139 "Options:\n" +
140 " --foreground, --fg\tRun the daemon in the foreground.\n" +
141 " --background, --bg\tRun the daemon in the background.\n" +
142 " --replace\t\tReplace a running daemon with a new instance.\n" +
143 " --debug\t\tWrite out debugging information.\n" +
144 " --debug-memory\tWrite out debugging information about memory use.\n" +
145 " --indexing-test-mode\tRun in foreground, and exit when fully indexed.\n" +
146 " --indexing-delay\tTime to wait before indexing. (Default 60 seconds)\n" +
147 " --backend\t\tConfigure which backends to use. Specifically:\n" +
148 " --backend <name>\tOnly start backend 'name'\n" +
149 " --backend +<name>\tAdditionally start backend 'name'\n" +
150 " --backend -<name>\tDisable backend 'name'\n" +
151 " --allow-backend\t(DEPRECATED) Start only the specific backend.\n" +
152 " --deny-backend\t(DEPRECATED) Deny a specific backend.\n" +
153 " --list-backends\tList all the available backends.\n" +
154 " --add-static-backend\tAdd a static backend by path.\n" +
155 " --disable-scheduler\tDisable the use of the scheduler.\n" +
156 " --help\t\tPrint this usage message.\n";
158 Console.WriteLine (usage);
161 public static bool StartupProcess ()
163 Log.Debug ("Beginning main loop");
165 // Profile our initialization
166 Stopwatch stopwatch = new Stopwatch ();
167 stopwatch.Start ();
169 // Fire up our server
170 if (! StartServer ()) {
171 if (arg_replace)
173 ReplaceExisting ();
175 else {
176 Logger.Log.Error ("Could not set up the listener for beagle requests. "
177 + "There is probably another beagled instance running. "
178 + "Use --replace to replace the running service");
179 Environment.Exit (1);
183 // Set up out-of-process indexing
184 LuceneQueryable.IndexerHook = new LuceneQueryable.IndexerCreator (RemoteIndexer.NewRemoteIndexer);
186 // Initialize synchronization to keep the indexes local if PathFinder.StorageDir
187 // is on a non-block device, or if BEAGLE_SYNCHRONIZE_LOCALLY is set
188 if ((! SystemInformation.IsPathOnBlockDevice (PathFinder.StorageDir) && Conf.Daemon.IndexSynchronization) ||
189 Environment.GetEnvironmentVariable ("BEAGLE_SYNCHRONIZE_LOCALLY") != null)
190 IndexSynchronization.Initialize ();
192 // Start the query driver.
193 Logger.Log.Debug ("Starting QueryDriver");
194 QueryDriver.Start ();
196 bool initially_on_battery = SystemInformation.UsingBattery && ! Conf.Indexing.IndexOnBattery;
198 // Start the Global Scheduler thread
199 if (! arg_disable_scheduler) {
200 if (! initially_on_battery) {
201 Logger.Log.Debug ("Starting Scheduler thread");
202 Scheduler.Global.Start ();
203 } else
204 Log.Debug ("Beagle started on battery, not starting scheduler thread");
207 // Poll the battery status so we can shut down the
208 // scheduler if needed. Ideally at some point this
209 // will become some sort of D-BUS signal, probably from
210 // something like gnome-power-manager.
211 prev_on_battery = initially_on_battery;
212 GLib.Timeout.Add (5000, CheckBatteryStatus);
214 // Start our Inotify threads
215 Inotify.Start ();
217 // Test if the FileAdvise stuff is working: This will print a
218 // warning if not. The actual advice calls will fail silently.
219 FileAdvise.TestAdvise ();
221 Conf.WatchForUpdates ();
223 stopwatch.Stop ();
225 Logger.Log.Debug ("Daemon initialization finished after {0}", stopwatch);
227 SystemInformation.LogMemoryUsage ();
229 if (arg_indexing_test_mode) {
230 Thread.Sleep (1000); // Ugly paranoia: wait a second for the backends to settle.
231 Logger.Log.Debug ("Running in indexing test mode");
232 Scheduler.Global.EmptyQueueEvent += OnEmptySchedulerQueue;
233 Scheduler.Global.Add (null); // pulse the scheduler
235 return false;
238 private static void OnEmptySchedulerQueue ()
240 Logger.Log.Debug ("Scheduler queue is empty: terminating immediately");
241 Shutdown.BeginShutdown ();
242 Environment.Exit (0); // Ugly work-around: We need to call Exit here to avoid deadlocking.
245 public static void Main (string[] args)
247 try {
248 DoMain (args);
249 } catch (Exception ex) {
250 Logger.Log.Error (ex, "Unhandled exception thrown. Exiting immediately.");
251 Environment.Exit (1);
255 [DllImport("libgobject-2.0.so.0")]
256 static extern void g_type_init ();
258 public static void DoMain (string[] args)
260 SystemInformation.InternalCallInitializer.Init ();
261 SystemInformation.SetProcessName ("beagled");
263 // Process the command-line arguments
264 bool arg_debug = false;
265 bool arg_debug_memory = false;
266 bool arg_fg = false;
268 int i = 0;
269 while (i < args.Length) {
271 string arg = args [i];
272 ++i;
273 string next_arg = i < args.Length ? args [i] : null;
275 switch (arg) {
276 case "-h":
277 case "--help":
278 PrintUsage ();
279 Environment.Exit (0);
280 break;
282 case "--mdb":
283 case "--mono-debug":
284 // Silently ignore these arguments: they get handled
285 // in the wrapper script.
286 break;
288 case "--list-backends":
289 Console.WriteLine ("Current available backends:");
290 Console.Write (QueryDriver.ListBackends ());
291 Environment.Exit (0);
292 break;
294 case "--fg":
295 case "--foreground":
296 arg_fg = true;
297 break;
299 case "--bg":
300 case "--background":
301 arg_fg = false;
302 break;
304 case "--replace":
305 arg_replace = true;
306 break;
308 case "--debug":
309 arg_debug = true;
310 break;
312 case "--heap-shot":
313 arg_heap_shot = true;
314 arg_debug = true;
315 arg_debug_memory = true;
316 break;
318 case "--heap-buddy":
319 case "--debug-memory":
320 arg_debug = true;
321 arg_debug_memory = true;
322 break;
324 case "--indexing-test-mode":
325 arg_indexing_test_mode = true;
326 arg_fg = true;
327 break;
329 case "--backend":
330 if (next_arg == null) {
331 Console.WriteLine ("--backend requires a backend name");
332 Environment.Exit (1);
333 break;
336 if (next_arg.StartsWith ("--")) {
337 Console.WriteLine ("--backend requires a backend name. Invalid name '{0}'", next_arg);
338 Environment.Exit (1);
339 break;
342 if (next_arg [0] != '+' && next_arg [0] != '-')
343 QueryDriver.OnlyAllow (next_arg);
344 else {
345 if (next_arg [0] == '+')
346 QueryDriver.Allow (next_arg.Substring (1));
347 else
348 QueryDriver.Deny (next_arg.Substring (1));
351 ++i; // we used next_arg
352 break;
354 case "--allow-backend":
355 // --allow-backend is deprecated, use --backends 'name' instead
356 // it will disable reading the list of enabled/disabled backends
357 // from conf and start the backend given
358 if (next_arg != null)
359 QueryDriver.OnlyAllow (next_arg);
360 ++i; // we used next_arg
361 break;
363 case "--deny-backend":
364 // deprecated: use --backends -'name' instead
365 if (next_arg != null)
366 QueryDriver.Deny (next_arg);
367 ++i; // we used next_arg
368 break;
370 case "--add-static-backend":
371 if (next_arg != null)
372 QueryDriver.AddStaticQueryable (next_arg);
373 ++i;
374 break;
376 case "--disable-scheduler":
377 arg_disable_scheduler = true;
378 break;
380 case "--indexing-delay":
381 if (next_arg != null) {
382 try {
383 QueryDriver.IndexingDelay = Int32.Parse (next_arg);
384 } catch {
385 Console.WriteLine ("'{0}' is not a valid number of seconds", next_arg);
386 Environment.Exit (1);
390 ++i;
391 break;
393 case "--autostarted":
394 // FIXME: This option is deprecated and will be removed in a future release.
395 break;
397 default:
398 Console.WriteLine ("Unknown argument '{0}'", arg);
399 Environment.Exit (1);
400 break;
405 if (arg_indexing_test_mode) {
406 LuceneQueryable.OptimizeRightAway = true;
410 // Bail out if we are trying to run as root
411 if (Environment.UserName == "root" && Environment.GetEnvironmentVariable ("SUDO_USER") != null) {
412 Console.WriteLine ("You appear to be running beagle using sudo. This can cause problems with");
413 Console.WriteLine ("permissions in your .beagle and .wapi directories if you later try to run");
414 Console.WriteLine ("as an unprivileged user. If you need to run beagle as root, please use");
415 Console.WriteLine ("'su -c' instead.");
416 Environment.Exit (-1);
419 if (Environment.UserName == "root" && ! Conf.Daemon.AllowRoot) {
420 Console.WriteLine ("You can not run beagle as root. Beagle is designed to run from your own");
421 Console.WriteLine ("user account. If you want to create multiuser or system-wide indexes, use");
422 Console.WriteLine ("the beagle-build-index tool.");
423 Console.WriteLine ();
424 Console.WriteLine ("You can override this setting using the beagle-config or beagle-settings tools.");
425 Environment.Exit (-1);
428 try {
429 string tmp = PathFinder.HomeDir;
430 } catch (Exception e) {
431 Console.WriteLine ("Unable to start the daemon: {0}", e.Message);
432 Environment.Exit (-1);
435 MainLoopThread = Thread.CurrentThread;
437 Log.Initialize (PathFinder.LogDir,
438 "Beagle",
439 // FIXME: We always turn on full debugging output! We are still
440 // debugging this code, after all...
441 //arg_debug ? LogLevel.Debug : LogLevel.Warn,
442 LogLevel.Debug,
443 arg_fg);
445 Logger.Log.Info ("Starting Beagle Daemon (version {0})", ExternalStringsHack.Version);
447 Logger.Log.Info ("Running on {0}", SystemInformation.MonoRuntimeVersion);
449 Logger.Log.Debug ("Command Line: {0}",
450 Environment.CommandLine != null ? Environment.CommandLine : "(null)");
452 if (! ExtendedAttribute.Supported) {
453 Logger.Log.Warn ("Extended attributes are not supported on this filesystem. " +
454 "Performance will suffer as a result.");
457 // Start our memory-logging thread
458 if (arg_debug_memory)
459 ExceptionHandlingThread.Start (new ThreadStart (LogMemoryUsage));
461 // Do BEAGLE_EXERCISE_THE_DOG_HARDER-related processing.
462 ExerciseTheDogHarder ();
464 // Initialize GObject type system
465 g_type_init ();
467 if (SystemInformation.XssInit ())
468 Logger.Log.Debug ("Established a connection to the X server");
469 else
470 Logger.Log.Debug ("Unable to establish a connection to the X server");
471 XSetIOErrorHandler (BeagleXIOErrorHandler);
473 QueryDriver.Init ();
474 Server.Init ();
476 SetupSignalHandlers ();
477 Shutdown.ShutdownEvent += OnShutdown;
479 main_loop = new MainLoop ();
480 Shutdown.RegisterMainLoop (main_loop);
482 // Defer all actual startup until the main loop is
483 // running. That way shutdowns during the startup
484 // process work correctly.
485 GLib.Idle.Add (new GLib.IdleHandler (StartupProcess));
487 // Start our event loop.
488 Logger.Log.Debug ("Starting main loop");
489 main_loop.Run ();
491 // We're out of the main loop now, join all the
492 // running threads so we can exit cleanly.
493 ExceptionHandlingThread.JoinAllThreads ();
495 // If we placed our sockets in a temp directory, try to clean it up
496 // Note: this may fail because the helper is still running
497 if (PathFinder.GetRemoteStorageDir (false) != PathFinder.StorageDir) {
498 try {
499 Directory.Delete (PathFinder.GetRemoteStorageDir (false));
500 } catch (IOException) { }
503 Log.Info ("Beagle daemon process shut down cleanly.");
506 /////////////////////////////////////////////////////////////////////////////
508 private static bool prev_on_battery = false;
510 private static bool CheckBatteryStatus ()
512 if (prev_on_battery && (! SystemInformation.UsingBattery || Conf.Indexing.IndexOnBattery)) {
513 if (! SystemInformation.UsingBattery)
514 Log.Info ("Deletected a switch from battery to AC power. Restarting scheduler.");
515 Scheduler.Global.Start ();
516 prev_on_battery = false;
517 } else if (! prev_on_battery && SystemInformation.UsingBattery && ! Conf.Indexing.IndexOnBattery) {
518 Log.Info ("Detected a switch from AC power to battery. Stopping scheduler.");
519 Scheduler.Global.Stop ();
520 prev_on_battery = true;
523 return true;
527 /////////////////////////////////////////////////////////////////////////////
529 private delegate int XIOErrorHandler (IntPtr display);
531 [DllImport ("libX11.so.6")]
532 extern static private int XSetIOErrorHandler (XIOErrorHandler handler);
534 private static int BeagleXIOErrorHandler (IntPtr display)
536 Logger.Log.Debug ("Lost our connection to the X server! Trying to shut down gracefully");
538 if (! Shutdown.ShutdownRequested)
539 Shutdown.BeginShutdown ();
541 Logger.Log.Debug ("Xlib is forcing us to exit!");
543 ExceptionHandlingThread.SpewLiveThreads ();
545 // Returning will cause xlib to exit immediately.
546 return 0;
549 /////////////////////////////////////////////////////////////////////////////
551 private static void SetupSignalHandlers ()
553 // Force OurSignalHandler to be JITed
554 OurSignalHandler (-1);
556 // Set up our signal handler
557 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGINT, OurSignalHandler);
558 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGTERM, OurSignalHandler);
559 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGUSR1, OurSignalHandler);
560 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGUSR2, OurSignalHandler);
562 // Ignore SIGPIPE
563 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGPIPE, Mono.Unix.Native.Stdlib.SIG_IGN);
566 // Mono signal handler allows setting of global variables;
567 // anything else e.g. function calls, even reentrant native methods are risky
568 private static void OurSignalHandler (int signal)
570 // This allows us to call OurSignalHandler w/o doing anything.
571 // We want to call it once to ensure that it is pre-JITed.
572 if (signal < 0)
573 return;
575 // Set shutdown flag to true so that other threads can stop initializing
576 if ((Mono.Unix.Native.Signum) signal != Mono.Unix.Native.Signum.SIGUSR1)
577 Shutdown.ShutdownRequested = true;
579 // Do all signal handling work in the main loop and not in the signal handler.
580 GLib.Idle.Add (new GLib.IdleHandler (delegate () { HandleSignal (signal); return false; }));
583 private static void HandleSignal (int signal)
585 Logger.Log.Debug ("Handling signal {0} ({1})", signal, (Mono.Unix.Native.Signum) signal);
587 // If we get SIGUSR1, turn the debugging level up.
588 if ((Mono.Unix.Native.Signum) signal == Mono.Unix.Native.Signum.SIGUSR1) {
589 LogLevel old_level = Log.Level;
590 Log.Level = LogLevel.Debug;
591 Log.Debug ("Moving from log level {0} to Debug", old_level);
594 // Send informational signals to the helper too.
595 if ((Mono.Unix.Native.Signum) signal == Mono.Unix.Native.Signum.SIGUSR1 ||
596 (Mono.Unix.Native.Signum) signal == Mono.Unix.Native.Signum.SIGUSR2) {
597 GLib.Idle.Add (new GLib.IdleHandler (delegate () { RemoteIndexer.SignalRemoteIndexer ((Mono.Unix.Native.Signum) signal); return false; }));
598 return;
601 Logger.Log.Debug ("Initiating shutdown in response to signal.");
602 Shutdown.BeginShutdown ();
605 /////////////////////////////////////////////////////////////////////////////
607 private static void OnShutdown ()
609 // Stop our Inotify threads
610 Inotify.Stop ();
612 // Stop the global scheduler and ask it to shutdown
613 Scheduler.Global.Stop (true);
615 // Stop the messaging server
616 if (server != null)
617 server.Stop ();
620 /////////////////////////////////////////////////////////////////////////////
623 private static ArrayList exercise_files = new ArrayList ();
625 private static void ExerciseTheDogHarder ()
627 string path;
628 path = Environment.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG_HARDER");
629 if (path == null)
630 return;
632 DirectoryInfo dir = new DirectoryInfo (path);
633 foreach (FileInfo file in dir.GetFiles ())
634 exercise_files.Add (file);
635 if (exercise_files.Count == 0)
636 return;
638 int N = 5;
639 if (N > exercise_files.Count)
640 N = exercise_files.Count;
642 for (int i = 0; i < N; ++i)
643 ExceptionHandlingThread.Start (new ThreadStart (ExerciseTheDogHarderWorker));
646 private static void ExerciseTheDogHarderWorker ()
648 Random rng = new Random ();
650 while (! Shutdown.ShutdownRequested) {
652 FileInfo file = null;
653 int i;
656 lock (exercise_files) {
657 do {
658 i = rng.Next (exercise_files.Count);
659 file = exercise_files [i] as FileInfo;
660 } while (file == null);
661 exercise_files [i] = null;
664 string target;
665 target = Path.Combine (PathFinder.HomeDir, "_HARDER_" + file.Name);
667 Logger.Log.Debug ("ETDH: Copying {0}", file.Name);
668 file.CopyTo (target, true);
670 lock (exercise_files)
671 exercise_files [i] = file;
673 Thread.Sleep (500 + rng.Next (500));