Oops, fix a broken part of the patch
[beagle.git] / beagled / BeagleDaemon.cs
blobb4bddf40497d3c3e02738d8f225f229a5d2dfd4c
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.Threading;
33 using Gtk;
35 using Beagle.Util;
37 #if ENABLE_WEBSERVICES
38 using Beagle.WebService;
39 #endif
41 namespace Beagle.Daemon {
42 class BeagleDaemon {
44 public static Thread MainLoopThread = 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;
52 public static bool StartServer ()
54 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 " --deny-backend\tDeny a specific backend.\n" +
114 " --allow-backend\tAllow a specific backend.\n" +
115 " --list-backends\tList all the available backends.\n" +
116 " --add-static-backend\tAdd a static backend by path.\n" +
117 " --disable-scheduler\tDisable the use of the scheduler.\n" +
118 " --help\t\tPrint this usage message.\n";
120 #if ENABLE_WEBSERVICES
121 usage += "\n" +
122 " --web-global\t\tAllow global access to the Web & WebService interfaces.\n" +
123 " --web-port\t\tPort to use for the internal web server.\n" +
124 " --web-root\t\tRoot directory to use for the internal web server.\n" +
125 " --web-disable\t\tDisable Web & WebServices functionality.\n";
126 #endif
128 Console.WriteLine (usage);
131 public static bool StartupProcess ()
133 // Profile our initialization
134 Stopwatch stopwatch = new Stopwatch ();
135 stopwatch.Start ();
137 SetupSignalHandlers ();
139 // Fire up our server
140 if (! StartServer ()) {
141 if (arg_replace)
143 #if ENABLE_WEBSERVICES
144 WebServiceBackEnd.Stop();
145 #endif
146 ReplaceExisting ();
148 else {
149 Logger.Log.Error ("Could not set up the listener for beagle requests. "
150 + "There is probably another beagled instance running. "
151 + "Use --replace to replace the running service");
152 Environment.Exit (1);
156 // Set up out-of-process indexing
157 if (Environment.GetEnvironmentVariable ("BEAGLE_ENABLE_IN_PROCESS_INDEXING") == null)
158 LuceneQueryable.IndexerHook = new LuceneQueryable.IndexerCreator (RemoteIndexer.NewRemoteIndexer);
160 // Initialize syncronization to keep the indexes local if PathFinder.HomeDir
161 // is on a non-block device, or if BEAGLE_SYNCHRONIZE_LOCALLY is set
162 if ((! SystemInformation.IsPathOnBlockDevice (PathFinder.HomeDir) && Conf.Daemon.IndexSynchronization) ||
163 Environment.GetEnvironmentVariable ("BEAGLE_SYNCHRONIZE_LOCALLY") != null)
164 IndexSynchronization.Initialize ();
166 // Start the query driver.
167 Logger.Log.Debug ("Starting QueryDriver");
168 QueryDriver.Start ();
170 // Start the Global Scheduler thread
171 if (! arg_disable_scheduler) {
172 Logger.Log.Debug ("Starting Scheduler thread");
173 Scheduler.Global.Start ();
176 // Start our Inotify threads
177 Inotify.Start ();
179 // Test if the FileAdvise stuff is working: This will print a
180 // warning if not. The actual advice calls will fail silently.
181 FileAdvise.TestAdvise ();
183 #if ENABLE_WEBSERVICES
184 //Beagle Web, WebService access initialization code:
185 WebServiceBackEnd.Start();
186 #endif
187 Shutdown.ShutdownEvent += OnShutdown;
189 Conf.WatchForUpdates ();
191 stopwatch.Stop ();
193 Logger.Log.Debug ("Daemon initialization finished after {0}", stopwatch);
195 if (arg_indexing_test_mode) {
196 Thread.Sleep (1000); // Ugly paranoia: wait a second for the backends to settle.
197 Logger.Log.Debug ("Running in indexing test mode");
198 Scheduler.Global.EmptyQueueEvent += OnEmptySchedulerQueue;
199 Scheduler.Global.Add (null); // pulse the scheduler
201 return false;
204 static void OnEmptySchedulerQueue ()
206 Logger.Log.Debug ("Scheduler queue is empty: terminating immediately");
207 Shutdown.BeginShutdown ();
208 Environment.Exit (0); // Ugly work-around: We need to call Exit here to avoid deadlocking.
211 public static void Main (string[] args)
213 try {
214 DoMain (args);
215 } catch (Exception ex) {
216 Logger.Log.Error ("Unhandled exception thrown. Exiting immediately.");
217 Logger.Log.Error (ex);
218 Environment.Exit (1);
222 public static void DoMain (string[] args)
224 SystemInformation.SetProcessName ("beagled");
226 // Process the command-line arguments
227 bool arg_debug = false;
228 bool arg_debug_memory = false;
229 bool arg_fg = false;
231 int i = 0;
232 while (i < args.Length) {
234 string arg = args [i];
235 ++i;
236 string next_arg = i < args.Length ? args [i] : null;
238 switch (arg) {
239 case "-h":
240 case "--help":
241 PrintUsage ();
242 Environment.Exit (0);
243 break;
245 case "--heap-buddy":
246 // Silently ignore the --heap-buddy argument: it gets handled
247 // in the wrapper script.
248 break;
250 case "--list-backends":
251 Console.WriteLine ("Current available backends:");
252 Console.Write (QueryDriver.ListBackends ());
253 Environment.Exit (0);
254 break;
256 case "--fg":
257 case "--foreground":
258 arg_fg = true;
259 break;
261 case "--bg":
262 case "--background":
263 arg_fg = false;
264 break;
266 case "--replace":
267 arg_replace = true;
268 break;
270 case "--debug":
271 arg_debug = true;
272 break;
274 case "--debug-memory":
275 arg_debug = true;
276 arg_debug_memory = true;
277 break;
279 case "--indexing-test-mode":
280 arg_indexing_test_mode = true;
281 arg_fg = true;
282 break;
284 case "--allow-backend":
285 if (next_arg != null)
286 QueryDriver.Allow (next_arg);
287 ++i; // we used next_arg
288 break;
290 case "--deny-backend":
291 if (next_arg != null)
292 QueryDriver.Deny (next_arg);
293 ++i; // we used next_arg
294 break;
296 case "--add-static-backend":
297 if (next_arg != null)
298 QueryDriver.AddStaticQueryable (next_arg);
299 ++i;
300 break;
302 case "--disable-scheduler":
303 arg_disable_scheduler = true;
304 break;
306 case "--indexing-delay":
307 if (next_arg != null) {
308 try {
309 QueryDriver.IndexingDelay = Int32.Parse (next_arg);
310 } catch {
311 Console.WriteLine ("'{0}' is not a valid number of seconds", next_arg);
312 Environment.Exit (1);
316 ++i;
317 break;
319 case "--autostarted":
320 if (! Conf.Searching.Autostart) {
321 Console.WriteLine ("Autostarting is disabled, not starting");
322 Environment.Exit (0);
324 break;
325 #if ENABLE_WEBSERVICES
326 case "--web-global":
327 WebServiceBackEnd.web_global = true;
328 WebServiceBackEnd.web_start = true;
329 break;
331 case "--web-port":
332 WebServiceBackEnd.web_port = next_arg;
333 ++i;
334 WebServiceBackEnd.web_start = true;
335 break;
337 case "--web-root":
338 WebServiceBackEnd.web_rootDir = next_arg;
339 ++i;
340 WebServiceBackEnd.web_start = true;
341 break;
343 case "--web-disable":
344 WebServiceBackEnd.web_start = false;
345 break;
346 #endif
347 default:
348 Console.WriteLine ("Unknown argument '{0}'", arg);
349 Environment.Exit (1);
350 break;
355 if (arg_indexing_test_mode) {
356 LuceneQueryable.OptimizeRightAway = true;
360 // Bail out if we are trying to run as root
361 if (Environment.UserName == "root" && ! Conf.Daemon.AllowRoot) {
362 Console.WriteLine ("You can not run beagle as root. Beagle is designed to run from your own");
363 Console.WriteLine ("user account. If you want to create multiuser or system-wide indexes, use");
364 Console.WriteLine ("the beagle-build-index tool.");
365 Console.WriteLine ();
366 Console.WriteLine ("You can override this setting using the beagle-config or beagle-settings tools.");
367 Environment.Exit (-1);
370 MainLoopThread = Thread.CurrentThread;
372 Log.Initialize (PathFinder.LogDir,
373 "Beagle",
374 // FIXME: We always turn on full debugging output! We are still
375 // debugging this code, after all...
376 //arg_debug ? LogLevel.Debug : LogLevel.Warn,
377 LogLevel.Debug,
378 arg_fg);
380 Logger.Log.Info ("Starting Beagle Daemon (version {0})", ExternalStringsHack.Version);
382 Logger.Log.Info ("Running on {0}", SystemInformation.MonoRuntimeVersion);
384 Logger.Log.Debug ("Command Line: {0}",
385 Environment.CommandLine != null ? Environment.CommandLine : "(null)");
387 if (! ExtendedAttribute.Supported) {
388 Logger.Log.Warn ("Extended attributes are not supported on this filesystem. " +
389 "Performance will suffer as a result.");
392 // Start our memory-logging thread
393 if (arg_debug_memory)
394 ExceptionHandlingThread.Start (new ThreadStart (LogMemoryUsage));
396 // Do BEAGLE_EXERCISE_THE_DOG_HARDER-related processing.
397 ExerciseTheDogHarder ();
399 Application.InitCheck ("beagled", ref args);
401 // Defer all actual startup until the main loop is
402 // running. That way shutdowns during the startup
403 // process work correctly.
404 GLib.Idle.Add (new GLib.IdleHandler (StartupProcess));
406 // Start our event loop.
407 Logger.Log.Debug ("Starting main loop");
409 Application.Run ();
411 // If we placed our sockets in a temp directory, try to clean it up
412 // Note: this may fail because the helper is still running
413 if (PathFinder.GetRemoteStorageDir (false) != PathFinder.StorageDir) {
414 try {
415 Directory.Delete (PathFinder.GetRemoteStorageDir (false));
416 } catch (IOException) { }
419 Logger.Log.Debug ("Leaving BeagleDaemon.Main");
421 if (arg_debug) {
422 Thread.Sleep (500);
423 ExceptionHandlingThread.SpewLiveThreads ();
428 /////////////////////////////////////////////////////////////////////////////
430 static void SetupSignalHandlers ()
432 // Force OurSignalHandler to be JITed
433 OurSignalHandler (-1);
435 // Set up our signal handler
436 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGINT, OurSignalHandler);
437 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGTERM, OurSignalHandler);
438 if (Environment.GetEnvironmentVariable("BEAGLE_THERE_BE_NO_QUITTIN") == null)
439 Mono.Unix.Native.Stdlib.signal (Mono.Unix.Native.Signum.SIGQUIT, OurSignalHandler);
442 // Our handler triggers an orderly shutdown when it receives a signal.
443 // However, this can be annoying if the process gets wedged during
444 // shutdown. To deal with that case, we make a note of the time when
445 // the first signal comes in, and we allow signals to unconditionally
446 // kill the process after 5 seconds have passed.
447 static DateTime signal_time = DateTime.MinValue;
448 static void OurSignalHandler (int signal)
450 // This allows us to call OurSignalHandler w/o doing anything.
451 // We want to call it once to ensure that it is pre-JITed.
452 if (signal < 0)
453 return;
455 Logger.Log.Debug ("Handling signal {0}", signal);
457 if (signal == (int) Mono.Unix.Native.Signum.SIGQUIT) {
458 ExceptionHandlingThread.AbortThreads ();
459 return;
462 bool first_signal = false;
463 if (signal_time == DateTime.MinValue) {
464 signal_time = DateTime.Now;
465 first_signal = true;
468 if (Shutdown.ShutdownRequested) {
470 if (first_signal) {
471 Logger.Log.Debug ("Shutdown already in progress.");
472 } else {
473 double t = (DateTime.Now - signal_time).TotalSeconds;
474 const double min_t = 5;
476 if (t < min_t) {
477 Logger.Log.Debug ("Signals can force an immediate shutdown in {0:0.00}s", min_t-t);
478 } else {
479 Logger.Log.Debug ("Forcing immediate shutdown.");
480 Environment.Exit (0);
484 } else {
485 Logger.Log.Debug ("Initiating shutdown in response to signal.");
486 Shutdown.BeginShutdown ();
490 /////////////////////////////////////////////////////////////////////////////
492 private static void OnShutdown ()
494 #if ENABLE_WEBSERVICES
495 WebServiceBackEnd.Stop();
496 #endif
497 // Stop our Inotify threads
498 Inotify.Stop ();
500 // Shut down the global scheduler
501 Scheduler.Global.Stop ();
503 // Stop the messaging server
504 server.Stop ();
507 /////////////////////////////////////////////////////////////////////////////
510 private static ArrayList exercise_files = new ArrayList ();
512 private static void ExerciseTheDogHarder ()
514 string path;
515 path = Environment.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG_HARDER");
516 if (path == null)
517 return;
519 DirectoryInfo dir = new DirectoryInfo (path);
520 foreach (FileInfo file in dir.GetFiles ())
521 exercise_files.Add (file);
522 if (exercise_files.Count == 0)
523 return;
525 int N = 5;
526 if (N > exercise_files.Count)
527 N = exercise_files.Count;
529 for (int i = 0; i < N; ++i)
530 ExceptionHandlingThread.Start (new ThreadStart (ExerciseTheDogHarderWorker));
533 private static void ExerciseTheDogHarderWorker ()
535 Random rng = new Random ();
537 while (! Shutdown.ShutdownRequested) {
539 FileInfo file = null;
540 int i;
543 lock (exercise_files) {
544 do {
545 i = rng.Next (exercise_files.Count);
546 file = exercise_files [i] as FileInfo;
547 } while (file == null);
548 exercise_files [i] = null;
551 string target;
552 target = Path.Combine (PathFinder.HomeDir, "_HARDER_" + file.Name);
554 Logger.Log.Debug ("ETDH: Copying {0}", file.Name);
555 file.CopyTo (target, true);
557 lock (exercise_files)
558 exercise_files [i] = file;
560 Thread.Sleep (500 + rng.Next (500));