Bump version number of index due to the IsChild property, should have done that before.
[beagle.git] / beagled / Server.cs
blob29863bf3ca39007d822de47961a10b376c57d51b
1 //
2 // Server.cs
3 //
4 // Copyright (C) 2005 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.Net.Sockets;
31 using System.Reflection;
32 using System.Threading;
33 using System.Xml.Serialization;
34 using Mono.Unix;
36 using Beagle.Util;
38 namespace Beagle.Daemon {
40 class ConnectionHandler {
42 private static int connection_count = 0;
43 private static XmlSerializer resp_serializer = null;
44 private static XmlSerializer req_serializer = null;
46 private object client_lock = new object ();
47 private object blocking_read_lock = new object ();
49 private UnixClient client;
50 private RequestMessageExecutor executor = null; // Only set in the keepalive case
51 private Thread thread;
52 private bool in_blocking_read;
54 public ConnectionHandler (UnixClient client)
56 this.client = client;
59 // Perform expensive serialization all at once. Do this before signal handler is setup.
60 public static void Init ()
62 resp_serializer = new XmlSerializer (typeof (ResponseWrapper), ResponseMessage.Types);
63 req_serializer = new XmlSerializer (typeof (RequestWrapper), RequestMessage.Types);
66 public bool SendResponse (ResponseMessage response)
68 lock (this.client_lock) {
69 if (this.client == null)
70 return false;
72 try {
73 #if ENABLE_XML_DUMP
74 MemoryStream mem_stream = new MemoryStream ();
75 XmlFu.SerializeUtf8 (resp_serializer, mem_stream, new ResponseWrapper (response));
76 mem_stream.Seek (0, SeekOrigin.Begin);
77 StreamReader r = new StreamReader (mem_stream);
78 Logger.Log.Debug ("Sending response:\n{0}\n", r.ReadToEnd ());
79 mem_stream.Seek (0, SeekOrigin.Begin);
80 mem_stream.WriteTo (this.client.GetStream ());
81 mem_stream.Close ();
82 #else
83 XmlFu.SerializeUtf8 (resp_serializer, this.client.GetStream (), new ResponseWrapper (response));
84 #endif
85 // Send an end of message marker
86 this.client.GetStream ().WriteByte (0xff);
87 this.client.GetStream ().Flush ();
88 } catch (Exception e) {
89 Logger.Log.Debug (e, "Caught an exception sending {0}. Shutting down socket.", response.GetType ());
90 return false;
93 return true;
97 public void CancelIfBlocking ()
99 // Work around some crappy .net behavior. We can't
100 // close a socket and have it exit out of a blocking
101 // read, so we have to abort the thread it's blocking
102 // in.
103 lock (this.blocking_read_lock) {
104 if (this.in_blocking_read) {
105 this.thread.Abort ();
110 public void Close ()
112 CancelIfBlocking ();
114 // It's important that we abort the thread before we
115 // grab the lock here and close the underlying
116 // UnixClient, or else we'd deadlock between here and
117 // the Read() in HandleConnection()
118 lock (this.client_lock) {
119 if (this.client != null) {
120 this.client.Close ();
121 this.client = null;
125 if (this.executor != null) {
126 this.executor.Cleanup ();
127 this.executor.AsyncResponseEvent -= OnAsyncResponse;
131 public void WatchCallback (IAsyncResult ar)
133 int bytes_read = 0;
135 try {
136 bytes_read = this.client.GetStream ().EndRead (ar);
137 } catch (SocketException) {
138 } catch (IOException) { }
140 if (bytes_read == 0)
141 Close ();
142 else
143 SetupWatch ();
146 private void SetupWatch ()
148 if (this.client == null) {
149 this.Close ();
150 return;
153 this.client.GetStream ().BeginRead (new byte[1024], 0, 1024,
154 new AsyncCallback (WatchCallback), null);
157 private void OnAsyncResponse (ResponseMessage response)
159 if (!SendResponse (response))
160 Close ();
163 public void HandleConnection ()
165 this.thread = Thread.CurrentThread;
167 RequestMessage req = null;
168 ResponseMessage resp = null;
170 bool force_close_connection = false;
172 // Read the data off the socket and store it in a
173 // temporary memory buffer. Once the end-of-message
174 // character has been read, discard remaining data
175 // and deserialize the request.
176 byte[] network_data = new byte [4096];
177 MemoryStream buffer_stream = new MemoryStream ();
178 int bytes_read, total_bytes = 0, end_index = -1;
180 // We use the network_data array as an object to represent this worker.
181 Shutdown.WorkerStart (network_data, String.Format ("HandleConnection ({0})", ++connection_count));
183 do {
184 bytes_read = 0;
186 try {
187 lock (this.blocking_read_lock)
188 this.in_blocking_read = true;
190 lock (this.client_lock) {
191 // The connection may have been closed within this loop.
192 if (this.client != null)
193 bytes_read = this.client.GetStream ().Read (network_data, 0, 4096);
196 lock (this.blocking_read_lock)
197 this.in_blocking_read = false;
198 } catch (Exception e) {
199 // Aborting the thread mid-read will
200 // cause an IOException to be thorwn,
201 // which sets the ThreadAbortException
202 // as its InnerException.
203 if (!(e is IOException || e is ThreadAbortException))
204 throw;
206 // Reset the unsightly ThreadAbortException
207 Thread.ResetAbort ();
209 Logger.Log.Debug ("Bailing out of HandleConnection -- shutdown requested");
210 Server.MarkHandlerAsKilled (this);
211 Shutdown.WorkerFinished (network_data);
212 return;
215 total_bytes += bytes_read;
217 if (bytes_read > 0) {
218 // 0xff signifies end of message
219 end_index = ArrayFu.IndexOfByte (network_data, (byte) 0xff);
221 buffer_stream.Write (network_data, 0,
222 end_index == -1 ? bytes_read : end_index);
224 } while (bytes_read > 0 && end_index == -1);
226 // Something just connected to our socket and then
227 // hung up. The IndexHelper (among other things) does
228 // this to check that a server is still running. It's
229 // no big deal, so just clean up and close without
230 // running any handlers.
231 if (total_bytes == 0) {
232 force_close_connection = true;
233 goto cleanup;
236 buffer_stream.Seek (0, SeekOrigin.Begin);
238 #if ENABLE_XML_DUMP
239 StreamReader r = new StreamReader (buffer_stream);
240 Logger.Log.Debug ("Received request:\n{0}\n", r.ReadToEnd ());
241 buffer_stream.Seek (0, SeekOrigin.Begin);
242 #endif
244 try {
245 RequestWrapper wrapper = (RequestWrapper) req_serializer.Deserialize (buffer_stream);
247 req = wrapper.Message;
248 } catch (InvalidOperationException e) {
249 // Undocumented: Xml Deserialization exceptions
250 if (e.InnerException != null)
251 resp = new ErrorResponse (e.InnerException);
252 else
253 resp = new ErrorResponse (e);
254 force_close_connection = true;
255 } catch (Exception e) {
256 resp = new ErrorResponse (e);
257 force_close_connection = true;
260 // If XmlSerializer can't deserialize the payload, we
261 // may get a null payload and not an exception. Or
262 // maybe the client just didn't send one.
263 if (req == null && resp == null) {
264 resp = new ErrorResponse ("Missing payload");
265 force_close_connection = true;
268 // And if there are no errors, execute the command
269 if (resp == null) {
271 RequestMessageExecutor exec;
272 exec = Server.GetExecutor (req);
274 if (exec == null) {
275 resp = new ErrorResponse (String.Format ("No handler available for {0}", req.GetType ()));
276 force_close_connection = true;
277 } else if (req.Keepalive) {
278 this.executor = exec;
279 exec.AsyncResponseEvent += OnAsyncResponse;
282 if (exec != null) {
283 try {
284 resp = exec.Execute (req);
285 } catch (Exception e) {
286 Log.Warn (e, "Caught exception trying to execute {0}. Sending error response", exec.GetType ());
287 resp = new ErrorResponse (e);
292 // It's okay if the response is null; this means
293 // that keepalive is set and that we'll be sending
294 // back responses asynchronously. First, enforce
295 // that the response is not null if keepalive isn't
296 // set.
297 if (resp == null && !req.Keepalive)
298 resp = new ErrorResponse ("No response available, but keepalive is not set");
300 if (resp != null) {
301 //Logger.Log.Debug ("Sending response of type {0}", resp.GetType ());
302 if (!this.SendResponse (resp))
303 force_close_connection = true;
306 cleanup:
307 buffer_stream.Close ();
309 if (force_close_connection || !req.Keepalive)
310 Close ();
311 else
312 SetupWatch ();
314 Server.MarkHandlerAsKilled (this);
315 Shutdown.WorkerFinished (network_data);
319 public class Server {
321 private static bool initialized = false;
323 private string socket_path;
324 private UnixListener listener;
325 private static Hashtable live_handlers = new Hashtable ();
326 private bool running = false;
328 static Server ()
330 foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies ())
331 ScanAssemblyForExecutors (assembly);
334 public Server (string name)
336 // Use the default name when passed null
337 if (name == null)
338 name = "socket";
340 this.socket_path = Path.Combine (PathFinder.GetRemoteStorageDir (true), name);
341 this.listener = new UnixListener (this.socket_path);
344 public Server () : this (null)
349 // Perform expensive serialization all at once. Do this before signal handler is setup.
350 public static void Init ()
352 Shutdown.ShutdownEvent += OnShutdown;
353 ConnectionHandler.Init ();
354 initialized = true;
357 static internal void MarkHandlerAsKilled (ConnectionHandler handler)
359 lock (live_handlers) {
360 live_handlers.Remove (handler);
364 private static void OnShutdown ()
366 lock (live_handlers) {
367 foreach (ConnectionHandler handler in live_handlers.Values) {
368 Logger.Log.Debug ("CancelIfBlocking {0}", handler);
369 handler.CancelIfBlocking ();
374 private void Run ()
376 this.listener.Start ();
377 this.running = true;
379 if (! Shutdown.WorkerStart (this, String.Format ("server '{0}'", socket_path)))
380 return;
382 while (this.running) {
383 UnixClient client;
384 try {
385 // This will block for an incoming connection.
386 // FIXME: But not really, it'll only wait a second.
387 // see the FIXME in UnixListener for more info.
388 client = this.listener.AcceptUnixClient ();
389 } catch (SocketException) {
390 // If the listener is stopped while we
391 // wait for a connection, a
392 // SocketException is thrown.
393 break;
396 // FIXME: This is a hack to work around a mono
397 // bug. See the FIXMEs in UnixListener.cs for
398 // more info, but client should never be null,
399 // because AcceptUnixClient() should be
400 // throwing a SocketException when the
401 // listener is shut down. So when that is
402 // fixed, remove the if conditional.
404 // If client is null, the socket timed out.
405 if (client != null) {
406 ConnectionHandler handler = new ConnectionHandler (client);
407 lock (live_handlers)
408 live_handlers [handler] = handler;
409 ExceptionHandlingThread.Start (new ThreadStart (handler.HandleConnection));
413 Shutdown.WorkerFinished (this);
415 Logger.Log.Debug ("Server '{0}' shut down", this.socket_path);
418 public void Start ()
420 if (!initialized)
421 throw new Exception ("Server must be initialized before starting");
423 if (!Shutdown.ShutdownRequested)
424 ExceptionHandlingThread.Start (new ThreadStart (this.Run));
427 public void Stop ()
429 if (this.running) {
430 this.running = false;
431 this.listener.Stop ();
433 File.Delete (this.socket_path);
436 //////////////////////////////////////////////////////////////////////////////
439 // Code to dispatch requests to the correct RequestMessageExecutor.
442 public delegate ResponseMessage RequestMessageHandler (RequestMessage msg);
444 // A simple wrapper class to turn a RequestMessageHandler delegate into
445 // a RequestMessageExecutor.
446 private class SimpleRequestMessageExecutor : RequestMessageExecutor {
448 RequestMessageHandler handler;
450 public SimpleRequestMessageExecutor (RequestMessageHandler handler)
452 this.handler = handler;
455 public override ResponseMessage Execute (RequestMessage req)
457 return this.handler (req);
461 static private Hashtable scanned_assemblies = new Hashtable ();
462 static private Hashtable request_type_to_handler = new Hashtable ();
463 static private Hashtable request_type_to_executor_type = new Hashtable ();
465 static public void RegisterRequestMessageHandler (Type request_type, RequestMessageHandler handler)
467 request_type_to_handler [request_type] = handler;
470 static public void ScanAssemblyForExecutors (Assembly assembly)
472 if (scanned_assemblies.Contains (assembly))
473 return;
474 scanned_assemblies [assembly] = assembly;
476 foreach (Type t in ReflectionFu.GetTypesFromAssemblyAttribute (assembly, typeof (RequestMessageExecutorTypesAttribute))) {
478 Attribute attr = Attribute.GetCustomAttribute (t, typeof (RequestMessageAttribute));
480 if (attr == null) {
481 Logger.Log.Warn ("No handler attribute for executor {0}", t);
482 continue;
485 RequestMessageAttribute pra = (RequestMessageAttribute) attr;
487 request_type_to_executor_type [pra.MessageType] = t;
491 static internal RequestMessageExecutor GetExecutor (RequestMessage req)
493 Type req_type = req.GetType ();
495 RequestMessageExecutor exec = null;
497 RequestMessageHandler handler;
498 handler = request_type_to_handler [req_type] as RequestMessageHandler;
500 if (handler != null) {
501 exec = new SimpleRequestMessageExecutor (handler);
502 } else {
503 Type t = request_type_to_executor_type [req_type] as Type;
504 if (t != null)
505 exec = (RequestMessageExecutor) Activator.CreateInstance (t);
508 return exec;