4 // Copyright (C) 2004-2006 Novell, Inc.
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.
28 using System
.Collections
;
29 using System
.Diagnostics
;
31 using System
.Reflection
;
32 using System
.Runtime
.InteropServices
;
33 using System
.Threading
;
34 using Thread
= System
.Threading
.Thread
;
38 using Log
= Beagle
.Util
.Log
;
40 namespace Beagle
.Daemon
{
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");
58 server
= new Server ("socket");
60 } catch (InvalidOperationException
) {
67 public static void ReplaceExisting ()
69 Logger
.Log
.Info ("Attempting to replace another beagled.");
72 ShutdownRequest request
= new ShutdownRequest ();
73 Logger
.Log
.Info ("Sending Shutdown");
75 // Give it a second to shut down the messaging server
77 } while (! StartServer ());
80 private static int prev_rss
= -1;
81 private static long prev_gc
= -1;
83 private static void MaybeSendSigprof (int rss
, long gc
)
85 bool send_sigprof
= false;
88 Log
.Debug ("Checking: RSS {0} old RSS {1} GC {2} old GC {3}",
89 rss
, prev_rss
, gc
, prev_gc
);
91 if (prev_rss
== -1 || prev_gc
== -1)
94 if (rss
- prev_rss
> 5000 ||
95 (double) rss
/ (double) prev_rss
> 1.05)
98 if ((double) gc
/ (double) prev_gc
> 1.05)
105 Log
.Debug ("Suspicious memory size change detected. Sending SIGPROF to ourself");
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
;
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 ();
130 private static void PrintUsage ()
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";
138 "Usage: beagled [OPTIONS]\n\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 ();
169 // Fire up our server
170 if (! StartServer ()) {
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 ();
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
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 ();
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
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
)
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;
269 while (i
< args
.Length
) {
271 string arg
= args
[i
];
273 string next_arg
= i
< args
.Length
? args
[i
] : null;
279 Environment
.Exit (0);
284 // Silently ignore these arguments: they get handled
285 // in the wrapper script.
288 case "--list-backends":
289 Console
.WriteLine ("Current available backends:");
290 Console
.Write (QueryDriver
.ListBackends ());
291 Environment
.Exit (0);
313 arg_heap_shot
= true;
315 arg_debug_memory
= true;
319 case "--debug-memory":
321 arg_debug_memory
= true;
324 case "--indexing-test-mode":
325 arg_indexing_test_mode
= true;
330 if (next_arg
== null) {
331 Console
.WriteLine ("--backend requires a backend name");
332 Environment
.Exit (1);
336 if (next_arg
.StartsWith ("--")) {
337 Console
.WriteLine ("--backend requires a backend name. Invalid name '{0}'", next_arg
);
338 Environment
.Exit (1);
342 if (next_arg
[0] != '+' && next_arg
[0] != '-')
343 QueryDriver
.OnlyAllow (next_arg
);
345 if (next_arg
[0] == '+')
346 QueryDriver
.Allow (next_arg
.Substring (1));
348 QueryDriver
.Deny (next_arg
.Substring (1));
351 ++i
; // we used next_arg
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
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
370 case "--add-static-backend":
371 if (next_arg
!= null)
372 QueryDriver
.AddStaticQueryable (next_arg
);
376 case "--disable-scheduler":
377 arg_disable_scheduler
= true;
380 case "--indexing-delay":
381 if (next_arg
!= null) {
383 QueryDriver
.IndexingDelay
= Int32
.Parse (next_arg
);
385 Console
.WriteLine ("'{0}' is not a valid number of seconds", next_arg
);
386 Environment
.Exit (1);
393 case "--autostarted":
394 if (! Conf
.Searching
.Autostart
) {
395 Console
.WriteLine ("Autostarting is disabled, not starting");
396 Environment
.Exit (0);
401 Console
.WriteLine ("Unknown argument '{0}'", arg
);
402 Environment
.Exit (1);
408 if (arg_indexing_test_mode
) {
409 LuceneQueryable
.OptimizeRightAway
= true;
413 // Bail out if we are trying to run as root
414 if (Environment
.UserName
== "root" && Environment
.GetEnvironmentVariable ("SUDO_USER") != null) {
415 Console
.WriteLine ("You appear to be running beagle using sudo. This can cause problems with");
416 Console
.WriteLine ("permissions in your .beagle and .wapi directories if you later try to run");
417 Console
.WriteLine ("as an unprivileged user. If you need to run beagle as root, please use");
418 Console
.WriteLine ("'su -c' instead.");
419 Environment
.Exit (-1);
422 if (Environment
.UserName
== "root" && ! Conf
.Daemon
.AllowRoot
) {
423 Console
.WriteLine ("You can not run beagle as root. Beagle is designed to run from your own");
424 Console
.WriteLine ("user account. If you want to create multiuser or system-wide indexes, use");
425 Console
.WriteLine ("the beagle-build-index tool.");
426 Console
.WriteLine ();
427 Console
.WriteLine ("You can override this setting using the beagle-config or beagle-settings tools.");
428 Environment
.Exit (-1);
432 string tmp
= PathFinder
.HomeDir
;
433 } catch (Exception e
) {
434 Console
.WriteLine ("Unable to start the daemon: {0}", e
.Message
);
435 Environment
.Exit (-1);
438 MainLoopThread
= Thread
.CurrentThread
;
440 Log
.Initialize (PathFinder
.LogDir
,
442 // FIXME: We always turn on full debugging output! We are still
443 // debugging this code, after all...
444 //arg_debug ? LogLevel.Debug : LogLevel.Warn,
448 Logger
.Log
.Info ("Starting Beagle Daemon (version {0})", ExternalStringsHack
.Version
);
450 Logger
.Log
.Info ("Running on {0}", SystemInformation
.MonoRuntimeVersion
);
452 Logger
.Log
.Debug ("Command Line: {0}",
453 Environment
.CommandLine
!= null ? Environment
.CommandLine
: "(null)");
455 if (! ExtendedAttribute
.Supported
) {
456 Logger
.Log
.Warn ("Extended attributes are not supported on this filesystem. " +
457 "Performance will suffer as a result.");
460 // Start our memory-logging thread
461 if (arg_debug_memory
)
462 ExceptionHandlingThread
.Start (new ThreadStart (LogMemoryUsage
));
464 // Do BEAGLE_EXERCISE_THE_DOG_HARDER-related processing.
465 ExerciseTheDogHarder ();
467 // Initialize GObject type system
470 if (SystemInformation
.XssInit ())
471 Logger
.Log
.Debug ("Established a connection to the X server");
473 Logger
.Log
.Debug ("Unable to establish a connection to the X server");
474 XSetIOErrorHandler (BeagleXIOErrorHandler
);
479 SetupSignalHandlers ();
480 Shutdown
.ShutdownEvent
+= OnShutdown
;
482 main_loop
= new MainLoop ();
483 Shutdown
.RegisterMainLoop (main_loop
);
485 // Defer all actual startup until the main loop is
486 // running. That way shutdowns during the startup
487 // process work correctly.
488 GLib
.Idle
.Add (new GLib
.IdleHandler (StartupProcess
));
490 // Start our event loop.
491 Logger
.Log
.Debug ("Starting main loop");
494 // We're out of the main loop now, join all the
495 // running threads so we can exit cleanly.
496 ExceptionHandlingThread
.JoinAllThreads ();
498 // If we placed our sockets in a temp directory, try to clean it up
499 // Note: this may fail because the helper is still running
500 if (PathFinder
.GetRemoteStorageDir (false) != PathFinder
.StorageDir
) {
502 Directory
.Delete (PathFinder
.GetRemoteStorageDir (false));
503 } catch (IOException
) { }
506 Log
.Info ("Beagle daemon process shut down cleanly.");
509 /////////////////////////////////////////////////////////////////////////////
511 private static bool prev_on_battery
= false;
513 private static bool CheckBatteryStatus ()
515 if (prev_on_battery
&& (! SystemInformation
.UsingBattery
|| Conf
.Indexing
.IndexOnBattery
)) {
516 if (! SystemInformation
.UsingBattery
)
517 Log
.Info ("Deletected a switch from battery to AC power. Restarting scheduler.");
518 Scheduler
.Global
.Start ();
519 prev_on_battery
= false;
520 } else if (! prev_on_battery
&& SystemInformation
.UsingBattery
&& ! Conf
.Indexing
.IndexOnBattery
) {
521 Log
.Info ("Detected a switch from AC power to battery. Stopping scheduler.");
522 Scheduler
.Global
.Stop ();
523 prev_on_battery
= true;
530 /////////////////////////////////////////////////////////////////////////////
532 private delegate int XIOErrorHandler (IntPtr display
);
534 [DllImport ("libX11.so.6")]
535 extern static private int XSetIOErrorHandler (XIOErrorHandler handler
);
537 private static int BeagleXIOErrorHandler (IntPtr display
)
539 Logger
.Log
.Debug ("Lost our connection to the X server! Trying to shut down gracefully");
541 if (! Shutdown
.ShutdownRequested
)
542 Shutdown
.BeginShutdown ();
544 Logger
.Log
.Debug ("Xlib is forcing us to exit!");
546 ExceptionHandlingThread
.SpewLiveThreads ();
548 // Returning will cause xlib to exit immediately.
552 /////////////////////////////////////////////////////////////////////////////
554 private static void SetupSignalHandlers ()
556 // Force OurSignalHandler to be JITed
557 OurSignalHandler (-1);
559 // Set up our signal handler
560 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGINT
, OurSignalHandler
);
561 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGTERM
, OurSignalHandler
);
562 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGUSR1
, OurSignalHandler
);
565 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGPIPE
, Mono
.Unix
.Native
.Stdlib
.SIG_IGN
);
568 // Mono signal handler allows setting of global variables;
569 // anything else e.g. function calls, even reentrant native methods are risky
570 private static void OurSignalHandler (int signal
)
572 // This allows us to call OurSignalHandler w/o doing anything.
573 // We want to call it once to ensure that it is pre-JITed.
577 // Set shutdown flag to true so that other threads can stop initializing
578 if ((Mono
.Unix
.Native
.Signum
) signal
!= Mono
.Unix
.Native
.Signum
.SIGUSR1
)
579 Shutdown
.ShutdownRequested
= true;
581 // Do all signal handling work in the main loop and not in the signal handler.
582 GLib
.Idle
.Add (new GLib
.IdleHandler (delegate () { HandleSignal (signal); return false; }
));
585 private static void HandleSignal (int signal
)
587 Logger
.Log
.Debug ("Handling signal {0} ({1})", signal
, (Mono
.Unix
.Native
.Signum
) signal
);
589 // If we get SIGUSR1, turn the debugging level up.
590 if ((Mono
.Unix
.Native
.Signum
) signal
== Mono
.Unix
.Native
.Signum
.SIGUSR1
) {
591 LogLevel old_level
= Log
.Level
;
592 Log
.Level
= LogLevel
.Debug
;
593 Log
.Debug ("Moving from log level {0} to Debug", old_level
);
594 GLib
.Idle
.Add (new GLib
.IdleHandler (delegate () { RemoteIndexer.SignalRemoteIndexer (); return false; }
));
598 Logger
.Log
.Debug ("Initiating shutdown in response to signal.");
599 Shutdown
.BeginShutdown ();
602 /////////////////////////////////////////////////////////////////////////////
604 private static void OnShutdown ()
606 // Stop our Inotify threads
609 // Stop the global scheduler and ask it to shutdown
610 Scheduler
.Global
.Stop (true);
612 // Stop the messaging server
617 /////////////////////////////////////////////////////////////////////////////
620 private static ArrayList exercise_files
= new ArrayList ();
622 private static void ExerciseTheDogHarder ()
625 path
= Environment
.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG_HARDER");
629 DirectoryInfo dir
= new DirectoryInfo (path
);
630 foreach (FileInfo file
in dir
.GetFiles ())
631 exercise_files
.Add (file
);
632 if (exercise_files
.Count
== 0)
636 if (N
> exercise_files
.Count
)
637 N
= exercise_files
.Count
;
639 for (int i
= 0; i
< N
; ++i
)
640 ExceptionHandlingThread
.Start (new ThreadStart (ExerciseTheDogHarderWorker
));
643 private static void ExerciseTheDogHarderWorker ()
645 Random rng
= new Random ();
647 while (! Shutdown
.ShutdownRequested
) {
649 FileInfo file
= null;
653 lock (exercise_files
) {
655 i
= rng
.Next (exercise_files
.Count
);
656 file
= exercise_files
[i
] as FileInfo
;
657 } while (file
== null);
658 exercise_files
[i
] = null;
662 target
= Path
.Combine (PathFinder
.HomeDir
, "_HARDER_" + file
.Name
);
664 Logger
.Log
.Debug ("ETDH: Copying {0}", file
.Name
);
665 file
.CopyTo (target
, true);
667 lock (exercise_files
)
668 exercise_files
[i
] = file
;
670 Thread
.Sleep (500 + rng
.Next (500));