Sorry for the combined patch. The changes became too inter-dependent.
[beagle.git] / beagled / Server.cs
blob4ef007d4e957b5f12c41872574a9b7c4e817977e
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 serializer = null;
44 private static XmlSerializer req_serializer = null;
47 private object client_lock = new object ();
48 private object blocking_read_lock = new object ();
50 private UnixClient client;
51 private RequestMessageExecutor executor = null; // Only set in the keepalive case
52 private Thread thread;
53 private bool in_blocking_read;
55 public ConnectionHandler (UnixClient client)
57 this.client = client;
60 // Perform expensive serialization all at once. Do this before signal handler is setup.
61 public static void Init ()
63 serializer = new XmlSerializer (typeof (ResponseWrapper), ResponseMessage.Types);
64 req_serializer = new XmlSerializer (typeof (RequestWrapper), RequestMessage.Types);
67 public bool SendResponse (ResponseMessage response)
69 lock (this.client_lock) {
70 if (this.client == null)
71 return false;
73 try {
74 #if ENABLE_XML_DUMP
75 MemoryStream mem_stream = new MemoryStream ();
76 XmlFu.SerializeUtf8 (serializer, mem_stream, new ResponseWrapper (response));
77 mem_stream.Seek (0, SeekOrigin.Begin);
78 StreamReader r = new StreamReader (mem_stream);
79 Logger.Log.Debug ("Sending response:\n{0}\n", r.ReadToEnd ());
80 mem_stream.Seek (0, SeekOrigin.Begin);
81 mem_stream.WriteTo (this.client.GetStream ());
82 mem_stream.Close ();
83 #else
84 XmlFu.SerializeUtf8 (serializer, this.client.GetStream (), new ResponseWrapper (response));
85 #endif
86 // Send an end of message marker
87 this.client.GetStream ().WriteByte (0xff);
88 this.client.GetStream ().Flush ();
89 } catch (Exception e) {
90 Logger.Log.Debug ("Caught an exception sending response; socket shut down: {0}", e.Message);
91 return false;
94 return true;
98 public void CancelIfBlocking ()
100 // Work around some crappy .net behavior. We can't
101 // close a socket and have it exit out of a blocking
102 // read, so we have to abort the thread it's blocking
103 // in.
104 lock (this.blocking_read_lock) {
105 if (this.in_blocking_read) {
106 this.thread.Abort ();
111 public void Close ()
113 CancelIfBlocking ();
115 // It's important that we abort the thread before we
116 // grab the lock here and close the underlying
117 // UnixClient, or else we'd deadlock between here and
118 // the Read() in HandleConnection()
119 lock (this.client_lock) {
120 if (this.client != null) {
121 this.client.Close ();
122 this.client = null;
126 if (this.executor != null) {
127 this.executor.Cleanup ();
128 this.executor.AsyncResponseEvent -= OnAsyncResponse;
132 public void WatchCallback (IAsyncResult ar)
134 int bytes_read = 0;
136 try {
137 bytes_read = this.client.GetStream ().EndRead (ar);
138 } catch (SocketException) {
139 } catch (IOException) { }
141 if (bytes_read == 0)
142 Close ();
143 else
144 SetupWatch ();
147 private void SetupWatch ()
149 if (this.client == null) {
150 this.Close ();
151 return;
154 this.client.GetStream ().BeginRead (new byte[1024], 0, 1024,
155 new AsyncCallback (WatchCallback), null);
158 private void OnAsyncResponse (ResponseMessage response)
160 if (!SendResponse (response))
161 Close ();
164 public void HandleConnection ()
166 this.thread = Thread.CurrentThread;
168 RequestMessage req = null;
169 ResponseMessage resp = null;
171 bool force_close_connection = false;
173 // Read the data off the socket and store it in a
174 // temporary memory buffer. Once the end-of-message
175 // character has been read, discard remaining data
176 // and deserialize the request.
177 byte[] network_data = new byte [4096];
178 MemoryStream buffer_stream = new MemoryStream ();
179 int bytes_read, total_bytes = 0, end_index = -1;
181 // We use the network_data array as an object to represent this worker.
182 Shutdown.WorkerStart (network_data, String.Format ("HandleConnection ({0})", ++connection_count));
184 do {
185 bytes_read = 0;
187 try {
188 lock (this.blocking_read_lock)
189 this.in_blocking_read = true;
191 lock (this.client_lock) {
192 // The connection may have been closed within this loop.
193 if (this.client != null)
194 bytes_read = this.client.GetStream ().Read (network_data, 0, 4096);
197 lock (this.blocking_read_lock)
198 this.in_blocking_read = false;
199 } catch (Exception e) {
200 // Aborting the thread mid-read will
201 // cause an IOException to be thorwn,
202 // which sets the ThreadAbortException
203 // as its InnerException.
204 if (!(e is IOException || e is ThreadAbortException))
205 throw;
207 Logger.Log.Debug ("Bailing out of HandleConnection -- shutdown requested");
208 Server.MarkHandlerAsKilled (this);
209 Shutdown.WorkerFinished (network_data);
210 return;
213 total_bytes += bytes_read;
215 if (bytes_read > 0) {
216 // 0xff signifies end of message
217 end_index = ArrayFu.IndexOfByte (network_data, (byte) 0xff);
219 buffer_stream.Write (network_data, 0,
220 end_index == -1 ? bytes_read : end_index);
222 } while (bytes_read > 0 && end_index == -1);
224 // Something just connected to our socket and then
225 // hung up. The IndexHelper (among other things) does
226 // this to check that a server is still running. It's
227 // no big deal, so just clean up and close without
228 // running any handlers.
229 if (total_bytes == 0) {
230 force_close_connection = true;
231 goto cleanup;
234 buffer_stream.Seek (0, SeekOrigin.Begin);
236 #if ENABLE_XML_DUMP
237 StreamReader r = new StreamReader (buffer_stream);
238 Logger.Log.Debug ("Received request:\n{0}\n", r.ReadToEnd ());
239 buffer_stream.Seek (0, SeekOrigin.Begin);
240 #endif
242 try {
243 RequestWrapper wrapper = (RequestWrapper) req_serializer.Deserialize (buffer_stream);
245 req = wrapper.Message;
246 } catch (Exception e) {
247 resp = new ErrorResponse (e);
248 force_close_connection = true;
251 // If XmlSerializer can't deserialize the payload, we
252 // may get a null payload and not an exception. Or
253 // maybe the client just didn't send one.
254 if (req == null && resp == null) {
255 resp = new ErrorResponse ("Missing payload");
256 force_close_connection = true;
259 // And if there are no errors, execute the command
260 if (resp == null) {
262 RequestMessageExecutor exec;
263 exec = Server.GetExecutor (req);
265 if (exec == null) {
266 resp = new ErrorResponse (String.Format ("No handler available for {0}", req.GetType ()));
267 force_close_connection = true;
268 } else if (req.Keepalive) {
269 this.executor = exec;
270 exec.AsyncResponseEvent += OnAsyncResponse;
273 if (exec != null)
274 resp = exec.Execute (req);
277 // It's okay if the response is null; this means
278 // that keepalive is set and that we'll be sending
279 // back responses asynchronously. First, enforce
280 // that the response is not null if keepalive isn't
281 // set.
282 if (resp == null && !req.Keepalive)
283 resp = new ErrorResponse ("No response available, but keepalive is not set");
285 if (resp != null) {
286 //Logger.Log.Debug ("Sending response of type {0}", resp.GetType ());
287 if (!this.SendResponse (resp))
288 force_close_connection = true;
291 cleanup:
292 buffer_stream.Close ();
294 if (force_close_connection || !req.Keepalive)
295 Close ();
296 else
297 SetupWatch ();
299 Server.MarkHandlerAsKilled (this);
300 Shutdown.WorkerFinished (network_data);
304 public class Server {
306 private string socket_path;
307 private UnixListener listener;
308 private static Hashtable live_handlers = new Hashtable ();
309 private bool running = false;
311 public Server (string name)
313 ScanAssemblyForExecutors (Assembly.GetCallingAssembly ());
315 // Use the default name when passed null
316 if (name == null)
317 name = "socket";
319 this.socket_path = Path.Combine (PathFinder.GetRemoteStorageDir (true), name);
320 this.listener = new UnixListener (this.socket_path);
323 public Server () : this (null)
328 // Perform expensive serialization all at once. Do this before signal handler is setup.
329 public static void Init ()
331 ScanAssemblyForExecutors (Assembly.GetExecutingAssembly ());
332 Shutdown.ShutdownEvent += OnShutdown;
333 ConnectionHandler.Init ();
336 static internal void MarkHandlerAsKilled (ConnectionHandler handler)
338 lock (live_handlers) {
339 live_handlers.Remove (handler);
343 private static void OnShutdown ()
345 lock (live_handlers) {
346 foreach (ConnectionHandler handler in live_handlers.Values) {
347 Logger.Log.Debug ("CancelIfBlocking {0}", handler);
348 handler.CancelIfBlocking ();
353 private void Run ()
355 this.listener.Start ();
356 this.running = true;
358 if (! Shutdown.WorkerStart (this, String.Format ("server '{0}'", socket_path)))
359 return;
361 while (this.running) {
362 UnixClient client;
363 try {
364 // This will block for an incoming connection.
365 // FIXME: But not really, it'll only wait a second.
366 // see the FIXME in UnixListener for more info.
367 client = this.listener.AcceptUnixClient ();
368 } catch (SocketException) {
369 // If the listener is stopped while we
370 // wait for a connection, a
371 // SocketException is thrown.
372 break;
375 // FIXME: This is a hack to work around a mono
376 // bug. See the FIXMEs in UnixListener.cs for
377 // more info, but client should never be null,
378 // because AcceptUnixClient() should be
379 // throwing a SocketException when the
380 // listener is shut down. So when that is
381 // fixed, remove the if conditional.
383 // If client is null, the socket timed out.
384 if (client != null) {
385 ConnectionHandler handler = new ConnectionHandler (client);
386 lock (live_handlers)
387 live_handlers [handler] = handler;
388 ExceptionHandlingThread.Start (new ThreadStart (handler.HandleConnection));
392 Shutdown.WorkerFinished (this);
394 Logger.Log.Debug ("Server '{0}' shut down", this.socket_path);
397 public void Start ()
399 if (!Shutdown.ShutdownRequested)
400 ExceptionHandlingThread.Start (new ThreadStart (this.Run));
403 public void Stop ()
405 if (this.running) {
406 this.running = false;
407 this.listener.Stop ();
409 File.Delete (this.socket_path);
412 //////////////////////////////////////////////////////////////////////////////
415 // Code to dispatch requests to the correct RequestMessageExecutor.
418 public delegate ResponseMessage RequestMessageHandler (RequestMessage msg);
420 // A simple wrapper class to turn a RequestMessageHandler delegate into
421 // a RequestMessageExecutor.
422 private class SimpleRequestMessageExecutor : RequestMessageExecutor {
424 RequestMessageHandler handler;
426 public SimpleRequestMessageExecutor (RequestMessageHandler handler)
428 this.handler = handler;
431 public override ResponseMessage Execute (RequestMessage req)
433 return this.handler (req);
437 static private Hashtable scanned_assemblies = new Hashtable ();
438 static private Hashtable request_type_to_handler = new Hashtable ();
439 static private Hashtable request_type_to_executor_type = new Hashtable ();
441 static public void RegisterRequestMessageHandler (Type request_type, RequestMessageHandler handler)
443 request_type_to_handler [request_type] = handler;
446 static public void ScanAssemblyForExecutors (Assembly assembly)
448 if (scanned_assemblies.Contains (assembly))
449 return;
450 scanned_assemblies [assembly] = assembly;
452 foreach (Type t in assembly.GetTypes ()) {
454 if (!t.IsSubclassOf (typeof (RequestMessageExecutor)))
455 continue;
457 // Yes, we know it doesn't have a RequestMessageAttribute
458 if (t == typeof (SimpleRequestMessageExecutor))
459 continue;
461 Attribute attr = Attribute.GetCustomAttribute (t, typeof (RequestMessageAttribute));
463 if (attr == null) {
464 Logger.Log.Warn ("No handler attribute for executor {0}", t);
465 continue;
468 RequestMessageAttribute pra = (RequestMessageAttribute) attr;
470 request_type_to_executor_type [pra.MessageType] = t;
474 static internal RequestMessageExecutor GetExecutor (RequestMessage req)
476 Type req_type = req.GetType ();
478 RequestMessageExecutor exec = null;
480 RequestMessageHandler handler;
481 handler = request_type_to_handler [req_type] as RequestMessageHandler;
483 if (handler != null) {
484 exec = new SimpleRequestMessageExecutor (handler);
485 } else {
486 Type t = request_type_to_executor_type [req_type] as Type;
487 if (t != null)
488 exec = (RequestMessageExecutor) Activator.CreateInstance (t);
491 return exec;