* KNotesQueryable.cs: Dont re-index all the notes when the notes file changes. Since...
[beagle.git] / beagled / BeagleDaemon.cs
blob4ef4f0ca31596516a2bd67706feaf6b13bbe65e9
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.IO;
30 using System.Reflection;
31 using System.Runtime.InteropServices;
32 using System.Threading;
33 using Thread = System.Threading.Thread;
34 using GLib;
36 using Beagle.Util;
37 using Log = Beagle.Util.Log;
39 namespace Beagle.Daemon {
40 class BeagleDaemon {
42 public static Thread MainLoopThread = null;
43 private static MainLoop main_loop = null;
45 private static Server server = null;
47 private static bool arg_replace = false;
48 private static bool arg_disable_scheduler = false;
49 private static bool arg_indexing_test_mode = false;
51 public static bool StartServer ()
53 Logger.Log.Debug ("Starting messaging server");
55 try {
56 server = new Server ("socket");
57 server.Start ();
58 } catch (InvalidOperationException) {
59 return false;
62 return true;
65 public static void ReplaceExisting ()
67 Logger.Log.Info ("Attempting to replace another beagled.");
69 do {
70 ShutdownRequest request = new ShutdownRequest ();
71 Logger.Log.Info ("Sending Shutdown");
72 request.Send ();
73 // Give it a second to shut down the messaging server
74 Thread.Sleep (1000);
75 } while (! StartServer ());
78 private static void LogMemoryUsage ()
80 while (! Shutdown.ShutdownRequested) {
81 int vm_size = SystemInformation.VmSize;
82 int vm_rss = SystemInformation.VmRss;
84 Logger.Log.Debug ("Memory usage: VmSize={0:.0} MB, VmRSS={1:.0} MB, GC.GetTotalMemory={2}",
85 vm_size/1024.0, vm_rss/1024.0, GC.GetTotalMemory (false));
87 if (vm_size > 300 * 1024) {
88 Logger.Log.Debug ("VmSize too large --- shutting down");
89 Shutdown.BeginShutdown ();
92 Thread.Sleep (5000);
96 private static void PrintUsage ()
98 string usage =
99 "beagled: The daemon to the Beagle search system.\n" +
100 "Web page: http://beagle-project.org\n" +
101 "Copyright (C) 2004-2006 Novell, Inc.\n\n";
103 usage +=
104 "Usage: beagled [OPTIONS]\n\n" +
105 "Options:\n" +
106 " --foreground, --fg\tRun the daemon in the foreground.\n" +
107 " --background, --bg\tRun the daemon in the background.\n" +
108 " --replace\t\tReplace a running daemon with a new instance.\n" +
109 " --debug\t\tWrite out debugging information.\n" +
110 " --debug-memory\tWrite out debugging information about memory use.\n" +
111 " --indexing-test-mode\tRun in foreground, and exit when fully indexed.\n" +
112 " --indexing-delay\tTime to wait before indexing. (Default 60 seconds)\n" +
113 " --backend\t\tConfigure which backends to use. Specifically:\n" +
114 " --backend <name>\tOnly start backend 'name'\n" +
115 " --backend +<name>\tAdditionally start backend 'name'\n" +
116 " --backend -<name>\tDisable backend 'name'\n" +
117 " --allow-backend\t(DEPRECATED) Start only the specific backend.\n" +
118 " --deny-backend\t(DEPRECATED) Deny a specific backend.\n" +
119 " --list-backends\tList all the available backends.\n" +
120 " --add-static-backend\tAdd a static backend by path.\n" +
121 " --disable-scheduler\tDisable the use of the scheduler.\n" +
122 " --help\t\tPrint this usage message.\n";
124 Console.WriteLine (usage);
127 public static bool StartupProcess ()
129 Log.Debug ("Beginning main loop");
131 // Profile our initialization
132 Stopwatch stopwatch = new Stopwatch ();
133 stopwatch.Start ();
135 // Fire up our server
136 if (! StartServer ()) {
137 if (arg_replace)
139 ReplaceExisting ();
141 else {
142 Logger.Log.Error ("Could not set up the listener for beagle requests. "
143 + "There is probably another beagled instance running. "
144 + "Use --replace to replace the running service");
145 Environment.Exit (1);
149 // Set up out-of-process indexing
150 if (Environment.GetEnvironmentVariable ("BEAGLE_ENABLE_IN_PROCESS_INDEXING") == null)
151 LuceneQueryable.IndexerHook = new LuceneQueryable.IndexerCreator (RemoteIndexer.NewRemoteIndexer);
153 // Initialize synchronization to keep the indexes local if PathFinder.HomeDir
154 // is on a non-block device, or if BEAGLE_SYNCHRONIZE_LOCALLY is set
155 if ((! SystemInformation.IsPathOnBlockDevice (PathFinder.HomeDir) && Conf.Daemon.IndexSynchronization) ||
156 Environment.GetEnvironmentVariable ("BEAGLE_SYNCHRONIZE_LOCALLY") != null)
157 IndexSynchronization.Initialize ();
159 // Start the query driver.
160 Logger.Log.Debug ("Starting QueryDriver");
161 QueryDriver.Start ();
163 bool initially_on_battery = SystemInformation.UsingBattery && ! Conf.Indexing.IndexOnBattery;
165 // Start the Global Scheduler thread
166 if (! arg_disable_scheduler) {
167 if (! initially_on_battery) {
168 Logger.Log.Debug ("Starting Scheduler thread");
169 Scheduler.Global.Start ();
170 } else
171 Log.Debug ("Beagle started on battery, not starting scheduler thread");
174 // Poll the battery status so we can shut down the
175 // scheduler if needed. Ideally at some point this
176 // will become some sort of D-BUS signal, probably from
177 // something like gnome-power-manager.
178 prev_on_battery = initially_on_battery;
179 GLib.Timeout.Add (5000, CheckBatteryStatus);
181 // Start our Inotify threads
182 Inotify.Start ();
184 // Test if the FileAdvise stuff is working: This will print a
185 // warning if not. The actual advice calls will fail silently.
186 FileAdvise.TestAdvise ();
188 Conf.WatchForUpdates ();
190 stopwatch.Stop ();
192 Logger.Log.Debug ("Daemon initialization finished after {0}", stopwatch);
194 if (arg_indexing_test_mode) {
195 Thread.Sleep (1000); // Ugly paranoia: wait a second for the backends to settle.
196 Logger.Log.Debug ("Running in indexing test mode");
197 Scheduler.Global.EmptyQueueEvent += OnEmptySchedulerQueue;
198 Scheduler.Global.Add (null); // pulse the scheduler
200 return false;
203 private static void OnEmptySchedulerQueue ()
205 Logger.Log.Debug ("Scheduler queue is empty: terminating immediately");
206 Shutdown.BeginShutdown ();
207 Environment.Exit (0); // Ugly work-around: We need to call Exit here to avoid deadlocking.
210 public static void Main (string[] args)
212 try {
213 DoMain (args);
214 } catch (Exception ex) {
215 Logger.Log.Error (ex, "Unhandled exception thrown. Exiting immediately.");
216 Environment.Exit (1);
220 [DllImport("libgobject-2.0.so.0")]
221 static extern void g_type_init ();
223 public static void DoMain (string[] args)
225 SystemInformation.InternalCallInitializer.Init ();
226 SystemInformation.SetProcessName ("beagled");
228 // Process the command-line arguments
229 bool arg_debug = false;
230 bool arg_debug_memory = false;
231 bool arg_fg = false;
233 int i = 0;
234 while (i < args.Length) {
236 string arg = args [i];
237 ++i;
238 string next_arg = i < args.Length ? args [i] : null;
240 switch (arg) {
241 case "-h":
242 case "--help":
243 PrintUsage ();
244 Environment.Exit (0);
245 break;
247 case "--heap-buddy":
248 case "--mdb":
249 // Silently ignore the --heap-buddy argument: it gets handled
250 // in the wrapper script.
251 break;
253 case "--list-backends":
254 Console.WriteLine ("Current available backends:");
255 Console.Write (QueryDriver.ListBackends ());
256 Environment.Exit (0);
257 break;
259 case "--fg":
260 case "--foreground":
261 arg_fg = true;
262 break;
264 case "--bg":
265 case "--background":
266 arg_fg = false;
267 break;
269 case "--replace":
270 arg_replace = true;
271 break;
273 case "--debug":
274 arg_debug = true;
275 break;
277 case "--debug-memory":
278 arg_debug = true;
279 arg_debug_memory = true;
280 break;
282 case "--indexing-test-mode":
283 arg_indexing_test_mode = true;
284 arg_fg = true;
285 break;
287 case "--backend":
288 if (next_arg == null) {
289 Console.WriteLine ("--backend requires a backend name");
290 Environment.Exit (1);
291 break;
294 if (next_arg.StartsWith ("--")) {
295 Console.WriteLine ("--backend requires a backend name. Invalid name '{0}'", next_arg);
296 Environment.Exit (1);
297 break;
300 if (next_arg [0] != '+' && next_arg [0] != '-')
301 QueryDriver.OnlyAllow (next_arg);
302 else {
303 if (next_arg [0] == '+')
304 QueryDriver.Allow (next_arg.Substring (1));
305 else
306 QueryDriver.Deny (next_arg.Substring (1));
309 ++i; // we used next_arg
310 break;
312 case "--allow-backend":
313 // --allow-backend is deprecated, use --backends 'name' instead
314 // it will disable reading the list of enabled/disabled backends
315 // from conf and start the backend given
316 if (next_arg != null)
317 QueryDriver.OnlyAllow (next_arg);
318 ++i; // we used next_arg
319 break;
321 case "--deny-backend":
322 // deprecated: use --backends -'name' instead
323 if (next_arg != null)
324 QueryDriver.Deny (next_arg);
325 ++i; // we used next_arg
326 break;
328 case "--add-static-backend":
329 if (next_arg != null)
330 QueryDriver.AddStaticQueryable (next_arg);
331 ++i;
332 break;
334 case "--disable-scheduler":
335 arg_disable_scheduler = true;
336 break;
338 case "--indexing-delay":
339 if (next_arg != null) {
340 try {
341 QueryDriver.IndexingDelay = Int32.Parse (next_arg);
342 } catch {
343 Console.WriteLine ("'{0}' is not a valid number of seconds", next_arg);
344 Environment.Exit (1);
348 ++i;
349 break;
351 case "--autostarted":
352 if (! Conf.Searching.Autostart) {
353 Console.WriteLine ("Autostarting is disabled, not starting");
354 Environment.Exit (0);
356 break;
358 default:
359 Console.WriteLine ("Unknown argument '{0}'", arg);
360 Environment.Exit (1);
361 break;
366 if (arg_indexing_test_mode) {
367 LuceneQueryable.OptimizeRightAway = true;
371 // Bail out if we are trying to run as root
372 if (Environment.UserName == "root" && Environment.GetEnvironmentVariable ("SUDO_USER") != null) {
373 Console.WriteLine ("You appear to be running beagle using sudo. This can cause problems with");
374 Console.WriteLine ("permissions in your .beagle and .wapi directories if you later try to run");
375 Console.WriteLine ("as an unprivileged user. If you need to run beagle as root, please use");
376 Console.WriteLine ("'su -c' instead.");
377 Environment.Exit (-1);
380 if (Environment.UserName == "root" && ! Conf.Daemon.AllowRoot) {
381 Console.WriteLine ("You can not run beagle as root. Beagle is designed to run from your own");
382 Console.WriteLine ("user account. If you want to create multiuser or system-wide indexes, use");
383 Console.WriteLine ("the beagle-build-index tool.");
384 Console.WriteLine ();
385 Console.WriteLine ("You can override this setting using the beagle-config or beagle-settings tools.");
386 Environment.Exit (-1);
389 try {
390 string tmp = PathFinder.HomeDir;
391 } catch (Exception e) {
392 Console.WriteLine ("Unable to start the daemon: {0}", e.Message);
393 Environment.Exit (-1);
396 MainLoopThread = Thread.CurrentThread;
398 Log.Initialize (PathFinder.LogDir,
399 "Beagle",
400 // FIXME: We always turn on full debugging output! We are still
401 // debugging this code, after all...
402 //arg_debug ? LogLevel.Debug : LogLevel.Warn,
403 LogLevel.Debug,
404 arg_fg);
406 Logger.Log.Info ("Starting Beagle Daemon (version {0})", ExternalStringsHack.Version);
408 Logger.Log.Info ("Running on {0}", SystemInformation.MonoRuntimeVersion);
410 Logger.Log.Debug ("Command Line: {0}",
411 Environment.CommandLine != null ? Environment.CommandLine : "(null)");
413 if (! ExtendedAttribute.Supported) {
414 Logger.Log.Warn ("Extended attributes are not supported on this filesystem. " +
415 "Performance will suffer as a result.");
418 // Start our memory-logging thread
419 if (arg_debug_memory)
420 ExceptionHandlingThread.Start (new ThreadStart (LogMemoryUsage));
422 // Do BEAGLE_EXERCISE_THE_DOG_HARDER-related processing.
423 ExerciseTheDogHarder ();
425 // Initialize GObject type system
426 g_type_init ();
428 if (SystemInformation.XssInit ())
429 Logger.Log.Debug ("Established a connection to the X server");
430 else
431 Logger.Log.Debug ("Unable to establish a connection to the X server");
432 XSetIOErrorHandler (BeagleXIOErrorHandler);
434 QueryDriver.Init ();
435 Server.Init ();
437 SetupSignalHandlers ();
438 Shutdown.ShutdownEvent += OnShutdown;
440 main_loop = new MainLoop ();
441 Shutdown.RegisterMainLoop (main_loop);
443 // Defer all actual startup until the main loop is
444 // running. That way shutdowns during the startup
445 // process work correctly.
446 GLib.Idle.Add (new GLib.IdleHandler (StartupProcess));
448 // Start our event loop.
449 Logger.Log.Debug ("Starting main loop");
450 main_loop.Run ();
452 // If we placed our sockets in a temp directory, try to clean it up
453 // Note: this may fail because the helper is still running
454 if (PathFinder.GetRemoteStorageDir (false) != PathFinder.StorageDir) {
455 try {
456 Directory.Delete (PathFinder.GetRemoteStorageDir (false));
457 } catch (IOException) { }
460 Logger.Log.Debug ("Leaving BeagleDaemon.Main");
462 if (arg_debug) {
463 Thread.Sleep (500);
464 ExceptionHandlingThread.SpewLiveThreads ();
468 /////////////////////////////////////////////////////////////////////////////
470 private static bool prev_on_battery = false;
472 private static bool CheckBatteryStatus ()
474 if (prev_on_battery && (! SystemInformation.UsingBattery || Conf.Indexing.IndexOnBattery)) {
475 if (! SystemInformation.UsingBattery)
476 Log.Info ("Deletected a switch from battery to AC power. Restarting scheduler.");
477 Scheduler.Global.Start ();
478 prev_on_battery = false;
479 } else if (! prev_on_battery && SystemInformation.UsingBattery && ! Conf.Indexing.IndexOnBattery) {
480 Log.Info ("Detected a switch from AC power to battery. Stopping scheduler.");
481 Scheduler.Global.Stop ();
482 prev_on_battery = true;
485 return true;
489 /////////////////////////////////////////////////////////////////////////////
491 private delegate int XIOErrorHandler (IntPtr display);
493 [DllImport ("libX11.so.6")]
494 extern static private int XSetIOErrorHandler (XIOErrorHandler handler);
496 private static int BeagleXIOErrorHandler (IntPtr display)
498 Logger.Log.Debug ("Lost our connection to the X server! Trying to shut down gracefully");
500 if (! Shutdown.ShutdownRequested)
501 Shutdown.BeginShutdown ();
503 Logger.Log.Debug ("Xlib is forcing us to exit!");
505 ExceptionHandlingThread.SpewLiveThreads ();
507 // Returning will cause xlib to exit immediately.
508 return 0;
511 /////////////////////////////////////////////////////////////////////////////
513 private static void SetupSignalHandlers ()
515 // Force OurSignalHandler to be JITed
516 OurSignalHandler (-1);
518 // Set up our signal handler
519 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGINT, OurSignalHandler);
520 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGTERM, OurSignalHandler);
521 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGUSR1, OurSignalHandler);
523 // Ignore SIGPIPE
524 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGPIPE, Mono.Unix.Native.Stdlib.SIG_IGN);
527 // Mono signal handler allows setting of global variables;
528 // anything else e.g. function calls, even reentrant native methods are risky
529 private static void OurSignalHandler (int signal)
531 // This allows us to call OurSignalHandler w/o doing anything.
532 // We want to call it once to ensure that it is pre-JITed.
533 if (signal < 0)
534 return;
536 // Set shutdown flag to true so that other threads can stop initializing
537 if ((Mono.Unix.Native.Signum) signal != Mono.Unix.Native.Signum.SIGUSR1)
538 Shutdown.ShutdownRequested = true;
540 // Do all signal handling work in the main loop and not in the signal handler.
541 GLib.Idle.Add (new GLib.IdleHandler (delegate () { HandleSignal (signal); return false; }));
544 private static void HandleSignal (int signal)
546 Logger.Log.Debug ("Handling signal {0} ({1})", signal, (Mono.Unix.Native.Signum) signal);
548 // If we get SIGUSR1, turn the debugging level up.
549 if ((Mono.Unix.Native.Signum) signal == Mono.Unix.Native.Signum.SIGUSR1) {
550 LogLevel old_level = Log.Level;
551 Log.Level = LogLevel.Debug;
552 Log.Debug ("Moving from log level {0} to Debug", old_level);
553 GLib.Idle.Add (new GLib.IdleHandler (delegate () { RemoteIndexer.SignalRemoteIndexer (); return false; }));
554 return;
557 Logger.Log.Debug ("Initiating shutdown in response to signal.");
558 Shutdown.BeginShutdown ();
561 /////////////////////////////////////////////////////////////////////////////
563 private static void OnShutdown ()
565 // Stop our Inotify threads
566 Inotify.Stop ();
568 // Stop the global scheduler and ask it to shutdown
569 Scheduler.Global.Stop (true);
571 // Stop the messaging server
572 if (server != null)
573 server.Stop ();
576 /////////////////////////////////////////////////////////////////////////////
579 private static ArrayList exercise_files = new ArrayList ();
581 private static void ExerciseTheDogHarder ()
583 string path;
584 path = Environment.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG_HARDER");
585 if (path == null)
586 return;
588 DirectoryInfo dir = new DirectoryInfo (path);
589 foreach (FileInfo file in dir.GetFiles ())
590 exercise_files.Add (file);
591 if (exercise_files.Count == 0)
592 return;
594 int N = 5;
595 if (N > exercise_files.Count)
596 N = exercise_files.Count;
598 for (int i = 0; i < N; ++i)
599 ExceptionHandlingThread.Start (new ThreadStart (ExerciseTheDogHarderWorker));
602 private static void ExerciseTheDogHarderWorker ()
604 Random rng = new Random ();
606 while (! Shutdown.ShutdownRequested) {
608 FileInfo file = null;
609 int i;
612 lock (exercise_files) {
613 do {
614 i = rng.Next (exercise_files.Count);
615 file = exercise_files [i] as FileInfo;
616 } while (file == null);
617 exercise_files [i] = null;
620 string target;
621 target = Path.Combine (PathFinder.HomeDir, "_HARDER_" + file.Name);
623 Logger.Log.Debug ("ETDH: Copying {0}", file.Name);
624 file.CopyTo (target, true);
626 lock (exercise_files)
627 exercise_files [i] = file;
629 Thread.Sleep (500 + rng.Next (500));