4 // Copyright (C) 2005 Novell, Inc.
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in all
16 // 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 FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 using System
.Collections
;
31 using SNS
= System
.Net
.Sockets
;
32 using System
.Runtime
.InteropServices
;
33 using System
.Threading
;
39 using Log
= Beagle
.Util
.Log
;
40 using Thread
= System
.Threading
.Thread
;
42 namespace Beagle
.IndexHelper
{
44 class IndexHelperTool
{
45 private static MainLoop main_loop
;
47 private static DateTime last_activity
;
48 private static Server server
;
50 // Current state with filtering.
51 public static Uri CurrentUri
;
52 public static Filter CurrentFilter
;
55 extern static private int unsetenv (string name
);
57 public static void Main (string [] args
)
61 } catch (Exception ex
) {
62 Logger
.Log
.Error (ex
, "Unhandled exception thrown. Exiting immediately.");
67 [DllImport("libgobject-2.0.so.0")]
68 static extern void g_type_init ();
70 private static void DoMain (string [] args
)
72 SystemInformation
.SetProcessName ("beagled-helper");
74 bool run_by_hand
= (Environment
.GetEnvironmentVariable ("BEAGLE_RUN_HELPER_BY_HAND") != null);
75 bool log_in_fg
= (Environment
.GetEnvironmentVariable ("BEAGLE_LOG_IN_THE_FOREGROUND_PLEASE") != null);
77 // FIXME: We always turn on full debugging output! We are still
78 // debugging this code, after all...
79 //bool debug = (Environment.GetEnvironmentVariable ("BEAGLE_DEBUG_FLAG_IS_SET") != null);
81 last_activity
= DateTime
.Now
;
83 Log
.Initialize (PathFinder
.LogDir
,
85 //debug ? LogLevel.Debug : LogLevel.Warn,
87 run_by_hand
|| log_in_fg
);
89 // Intentionally unset DISPLAY so that we can't connect
90 // to the X server and aren't influenced by it if it
91 // goes away. It's important to do this before
92 // Application.InitCheck(), since that's what makes the
94 //unsetenv ("DISPLAY");
96 SystemInformation
.XssInit (false);
98 // Initialize GObject type system
103 SetupSignalHandlers ();
105 Shutdown
.ShutdownEvent
+= OnShutdown
;
107 main_loop
= new MainLoop ();
108 Shutdown
.RegisterMainLoop (main_loop
);
111 Logger
.Log
.Debug ("Starting messaging server");
112 bool server_has_been_started
= false;
114 server
= new Server ("socket-helper");
116 server_has_been_started
= true;
117 } catch (InvalidOperationException ex
) {
118 Logger
.Log
.Error (ex
, "Couldn't start server. Exiting immediately.");
121 if (server_has_been_started
) {
122 // Set the IO priority to idle so we don't slow down the system
123 if (Environment
.GetEnvironmentVariable ("BEAGLE_EXERCISE_THE_DOG") == null) {
124 IoPriority
.ReduceIoPriority ();
126 int prio
= Mono
.Unix
.Native
.Syscall
.nice (15);
129 Log
.Warn ("Unable to renice helper to +15");
131 Log
.Debug ("Reniced helper to +15");
133 Log
.Debug ("Helper was already niced to {0}, not renicing to +15", prio
);
135 Log
.Info ("BEAGLE_EXERCISE_THE_DOG is set");
137 // Start the monitor thread, which keeps an eye on memory usage and idle time.
138 ExceptionHandlingThread
.Start (new ThreadStart (MemoryAndIdleMonitorWorker
));
140 // Start a thread that watches the daemon and begins a shutdown
142 ExceptionHandlingThread
.Start (new ThreadStart (DaemonMonitorWorker
));
144 // Start the main loop
147 ExceptionHandlingThread
.JoinAllThreads ();
149 // If we placed our sockets in a temp directory, try to clean it up
150 // Note: this may fail because the daemon is still running
151 if (PathFinder
.GetRemoteStorageDir (false) != PathFinder
.StorageDir
) {
153 Directory
.Delete (PathFinder
.GetRemoteStorageDir (false));
154 } catch (IOException
) { }
157 Log
.Info ("Index helper process shut down cleanly.");
161 public static void ReportActivity ()
163 last_activity
= DateTime
.Now
;
166 private static void MemoryAndIdleMonitorWorker ()
168 int vmrss_original
= SystemInformation
.VmRss
;
170 const double max_idle_time
= 30; // minutes
172 const double threshold
= 5.0;
173 const int max_request_count
= 0;
176 while (! Shutdown
.ShutdownRequested
) {
179 idle_time
= (DateTime
.Now
- last_activity
).TotalMinutes
;
180 if (idle_time
> max_idle_time
&& RemoteIndexerExecutor
.Count
> 0) {
181 Logger
.Log
.Debug ("No activity for {0:0.0} minutes, shutting down", idle_time
);
182 Shutdown
.BeginShutdown ();
186 // Check resident memory usage
187 int vmrss
= SystemInformation
.VmRss
;
188 double size
= vmrss
/ (double) vmrss_original
;
189 if (vmrss
!= last_vmrss
)
190 Logger
.Log
.Debug ("Helper Size: VmRSS={0:0.0} MB, size={1:0.00}, {2:0.0}%",
191 vmrss
/1024.0, size
, 100.0 * (size
- 1) / (threshold
- 1));
194 || (max_request_count
> 0 && RemoteIndexerExecutor
.Count
> max_request_count
)) {
195 if (RemoteIndexerExecutor
.Count
> 0) {
196 Logger
.Log
.Debug ("Process too big, shutting down!");
197 Shutdown
.BeginShutdown ();
200 // Paranoia: don't shut down if we haven't done anything yet
201 Logger
.Log
.Debug ("Deferring shutdown until we've actually done something.");
210 private static void DaemonMonitorWorker ()
212 string storage_dir
= PathFinder
.GetRemoteStorageDir (false);
214 if (storage_dir
== null) {
215 Logger
.Log
.Debug ("The daemon doesn't appear to have started");
216 Logger
.Log
.Debug ("Shutting down helper.");
217 Shutdown
.BeginShutdown ();
221 // FIXME: We shouldn't need to know the name of the daemon's socket.
223 socket_name
= Path
.Combine (storage_dir
, "socket");
227 socket
= new SNS
.Socket (SNS
.AddressFamily
.Unix
, SNS
.SocketType
.Stream
, 0);
228 socket
.Connect (new Mono
.Unix
.UnixEndPoint (socket_name
));
230 ArrayList socket_list
= new ArrayList ();
232 while (! Shutdown
.ShutdownRequested
) {
233 socket_list
.Add (socket
);
234 SNS
.Socket
.Select (socket_list
, null, null, 1000000); // 1000000 microseconds = 1 second
235 if (socket_list
.Count
!= 0) {
236 Logger
.Log
.Debug ("The daemon appears to have gone away.");
237 Logger
.Log
.Debug ("Shutting down helper.");
238 Shutdown
.BeginShutdown ();
241 } catch (SNS
.SocketException
) {
242 Logger
.Log
.Debug ("Caught a SocketException while trying to monitor the daemon");
243 Logger
.Log
.Debug ("Shutting down");
244 Shutdown
.BeginShutdown ();
248 /////////////////////////////////////////////////////////////////////////////
250 private static void SetupSignalHandlers ()
252 // Force OurSignalHandler to be JITed
253 OurSignalHandler (-1);
255 // Set up our signal handler
256 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGINT
, OurSignalHandler
);
257 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGTERM
, OurSignalHandler
);
258 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGUSR1
, OurSignalHandler
);
259 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGUSR2
, OurSignalHandler
);
262 Mono
.Unix
.Native
.Stdlib
.signal (Mono
.Unix
.Native
.Signum
.SIGPIPE
, Mono
.Unix
.Native
.Stdlib
.SIG_IGN
);
265 // Our handler triggers an orderly shutdown when it receives a signal.
266 // However, this can be annoying if the process gets wedged during
267 // shutdown. To deal with that case, we make a note of the time when
268 // the first signal comes in, and we allow signals to unconditionally
269 // kill the process after 5 seconds have passed.
270 private static DateTime signal_time
= DateTime
.MinValue
;
271 private static void OurSignalHandler (int signal
)
273 // This allows us to call OurSignalHandler w/o doing anything.
274 // We want to call it once to ensure that it is pre-JITed.
278 // SIGUSR1 and SIGUSR2 are informational signals. For other
279 // signals that we handle, set the shutdown flag to true so
280 // that other threads can stop initializing
281 if ((Mono
.Unix
.Native
.Signum
) signal
!= Mono
.Unix
.Native
.Signum
.SIGUSR1
&&
282 (Mono
.Unix
.Native
.Signum
) signal
!= Mono
.Unix
.Native
.Signum
.SIGUSR2
)
283 Shutdown
.ShutdownRequested
= true;
285 // Do all signal handling work in the main loop and not in the signal handler.
286 GLib
.Idle
.Add (new GLib
.IdleHandler (delegate () { HandleSignal (signal); return false; }
));
289 private static void HandleSignal (int signal
)
291 Log
.Warn ("Handling signal {0} ({1})", signal
, (Mono
.Unix
.Native
.Signum
) signal
);
293 // If we get SIGUSR1, turn the debugging level up.
294 if ((Mono
.Unix
.Native
.Signum
) signal
== Mono
.Unix
.Native
.Signum
.SIGUSR1
) {
295 LogLevel old_level
= Log
.Level
;
296 Log
.Level
= LogLevel
.Debug
;
297 Log
.Debug ("Moving from log level {0} to Debug", old_level
);
300 string span
= StringFu
.TimeSpanToString (DateTime
.Now
- last_activity
);
302 if (CurrentUri
== null)
303 Log
.Warn ("Filtering status ({0} ago): no document is currently being filtered.", span
);
304 else if (CurrentFilter
== null)
305 Log
.Warn ("Filtering status ({0} ago): determining filter for {1}", span
, CurrentUri
);
307 Log
.Warn ("Filtering status ({0} ago): filtering {1} with {2}", span
, CurrentUri
, CurrentFilter
);
309 // Don't shut down on information signals (SIGUSR1 and SIGUSR2)
310 if ((Mono
.Unix
.Native
.Signum
) signal
== Mono
.Unix
.Native
.Signum
.SIGUSR1
||
311 (Mono
.Unix
.Native
.Signum
) signal
== Mono
.Unix
.Native
.Signum
.SIGUSR2
)
314 Logger
.Log
.Debug ("Initiating shutdown in response to signal.");
315 Shutdown
.BeginShutdown ();
318 private static void OnShutdown ()