Thumbnail file hits. Based on a patch from D Bera
[beagle.git] / beagled / Server.cs
blob7e6f7b0f809e9efeeae8440c2e89fad688e6c865
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;
35 using Beagle.Util;
37 namespace Beagle.Daemon {
39 class ConnectionHandler {
41 private static int connection_count = 0;
42 private static XmlSerializer serializer = new XmlSerializer (typeof (ResponseWrapper), ResponseMessage.Types);
44 private object client_lock = new object ();
45 private object blocking_read_lock = new object ();
47 private UnixClient client;
48 private RequestMessageExecutor executor = null; // Only set in the keepalive case
49 private Thread thread;
50 private bool in_blocking_read;
52 public ConnectionHandler (UnixClient client)
54 this.client = client;
57 public bool SendResponse (ResponseMessage response)
59 lock (this.client_lock) {
60 if (this.client == null)
61 return false;
63 try {
64 serializer.Serialize (this.client.GetStream (), new ResponseWrapper (response));
65 // Send an end of message marker
66 this.client.GetStream ().WriteByte (0xff);
67 this.client.GetStream ().Flush ();
68 } catch (Exception e) {
69 Logger.Log.Debug ("Caught an exception sending response; socket shut down: {0}", e.Message);
70 return false;
73 return true;
77 public void CancelIfBlocking ()
79 // Work around some crappy .net behavior. We can't
80 // close a socket and have it exit out of a blocking
81 // read, so we have to abort the thread it's blocking
82 // in.
83 lock (this.blocking_read_lock) {
84 if (this.in_blocking_read) {
85 this.thread.Abort ();
90 public void Close ()
92 CancelIfBlocking ();
94 // It's important that we abort the thread before we
95 // grab the lock here and close the underlying
96 // UnixClient, or else we'd deadlock between here and
97 // the Read() in HandleConnection()
98 lock (this.client_lock) {
99 if (this.client != null) {
100 this.client.Close ();
101 this.client = null;
105 if (this.executor != null) {
106 this.executor.Cleanup ();
107 this.executor.AsyncResponseEvent -= OnAsyncResponse;
111 public void WatchCallback (IAsyncResult ar)
113 int bytes_read = 0;
115 try {
116 bytes_read = this.client.GetStream ().EndRead (ar);
117 } catch (SocketException) {
118 } catch (IOException) { }
120 if (bytes_read == 0)
121 Close ();
122 else
123 SetupWatch ();
126 private void SetupWatch ()
128 if (this.client == null) {
129 this.Close ();
130 return;
133 this.client.GetStream ().BeginRead (new byte[1024], 0, 1024,
134 new AsyncCallback (WatchCallback), null);
137 private void OnAsyncResponse (ResponseMessage response)
139 if (!SendResponse (response))
140 Close ();
143 static XmlSerializer req_serializer = new XmlSerializer (typeof (RequestWrapper), RequestMessage.Types);
145 public void HandleConnection ()
147 this.thread = Thread.CurrentThread;
149 RequestMessage req = null;
150 ResponseMessage resp = null;
152 bool force_close_connection = false;
154 // Read the data off the socket and store it in a
155 // temporary memory buffer. Once the end-of-message
156 // character has been read, discard remaining data
157 // and deserialize the request.
158 byte[] network_data = new byte [4096];
159 MemoryStream buffer_stream = new MemoryStream ();
160 int bytes_read, total_bytes = 0, end_index = -1;
162 // We use the network_data array as an object to represent this worker.
163 Shutdown.WorkerStart (network_data, String.Format ("HandleConnection ({0})", ++connection_count));
165 do {
166 bytes_read = 0;
168 try {
169 lock (this.blocking_read_lock)
170 this.in_blocking_read = true;
172 lock (this.client_lock) {
173 // The connection may have been closed within this loop.
174 if (this.client != null)
175 bytes_read = this.client.GetStream ().Read (network_data, 0, 4096);
178 lock (this.blocking_read_lock)
179 this.in_blocking_read = false;
180 } catch (Exception e) {
181 // Aborting the thread mid-read will
182 // cause an IOException to be thorwn,
183 // which sets the ThreadAbortException
184 // as its InnerException.
185 if (!(e is IOException || e is ThreadAbortException))
186 throw;
188 Logger.Log.Debug ("Bailing out of HandleConnection -- shutdown requested");
189 Server.MarkHandlerAsKilled (this);
190 Shutdown.WorkerFinished (network_data);
191 return;
194 total_bytes += bytes_read;
196 if (bytes_read > 0) {
197 // 0xff signifies end of message
198 end_index = ArrayFu.IndexOfByte (network_data, (byte) 0xff);
200 buffer_stream.Write (network_data, 0,
201 end_index == -1 ? bytes_read : end_index);
203 } while (bytes_read > 0 && end_index == -1);
205 // Something just connected to our socket and then
206 // hung up. The IndexHelper (among other things) does
207 // this to check that a server is still running. It's
208 // no big deal, so just clean up and close without
209 // running any handlers.
210 if (total_bytes == 0) {
211 force_close_connection = true;
212 goto cleanup;
215 buffer_stream.Seek (0, SeekOrigin.Begin);
217 try {
218 RequestWrapper wrapper = (RequestWrapper) req_serializer.Deserialize (buffer_stream);
220 req = wrapper.Message;
221 } catch (Exception e) {
222 resp = new ErrorResponse (e);
223 force_close_connection = true;
226 // If XmlSerializer can't deserialize the payload, we
227 // may get a null payload and not an exception. Or
228 // maybe the client just didn't send one.
229 if (req == null && resp == null) {
230 resp = new ErrorResponse ("Missing payload");
231 force_close_connection = true;
234 // And if there are no errors, execute the command
235 if (resp == null) {
237 RequestMessageExecutor exec;
238 exec = Server.GetExecutor (req);
240 if (exec == null) {
241 resp = new ErrorResponse (String.Format ("No handler available for {0}", req.GetType ()));
242 force_close_connection = true;
243 } else if (req.Keepalive) {
244 this.executor = exec;
245 exec.AsyncResponseEvent += OnAsyncResponse;
248 resp = exec.Execute (req);
251 // It's okay if the response is null; this means
252 // that keepalive is set and that we'll be sending
253 // back responses asynchronously. First, enforce
254 // that the response is not null if keepalive isn't
255 // set.
256 if (resp == null && !req.Keepalive)
257 resp = new ErrorResponse ("No response available, but keepalive is not set");
259 if (resp != null) {
260 //Logger.Log.Debug ("Sending response of type {0}", resp.GetType ());
261 if (!this.SendResponse (resp))
262 force_close_connection = true;
265 cleanup:
266 buffer_stream.Close ();
268 if (force_close_connection || !req.Keepalive)
269 Close ();
270 else
271 SetupWatch ();
273 Server.MarkHandlerAsKilled (this);
274 Shutdown.WorkerFinished (network_data);
278 public class Server {
280 private string socket_path;
281 private UnixListener listener;
282 private static Hashtable live_handlers = new Hashtable ();
283 private bool running = false;
285 public Server (string name)
287 ScanAssemblyForExecutors (Assembly.GetCallingAssembly ());
289 // Use the default name when passed null
290 if (name == null)
291 name = "socket";
293 this.socket_path = Path.Combine (PathFinder.GetRemoteStorageDir (true), name);
294 this.listener = new UnixListener (this.socket_path);
297 public Server () : this (null)
302 static Server ()
304 ScanAssemblyForExecutors (Assembly.GetExecutingAssembly ());
306 Shutdown.ShutdownEvent += OnShutdown;
309 static internal void MarkHandlerAsKilled (ConnectionHandler handler)
311 lock (live_handlers) {
312 live_handlers.Remove (handler);
316 private static void OnShutdown ()
318 lock (live_handlers) {
319 foreach (ConnectionHandler handler in live_handlers.Values) {
320 Logger.Log.Debug ("CancelIfBlocking {0}", handler);
321 handler.CancelIfBlocking ();
326 private void Run ()
328 this.listener.Start ();
329 this.running = true;
331 Shutdown.WorkerStart (this, String.Format ("server '{0}'", socket_path));
333 while (this.running) {
334 UnixClient client;
335 try {
336 // This will block for an incoming connection.
337 // FIXME: But not really, it'll only wait a second.
338 // see the FIXME in UnixListener for more info.
339 client = this.listener.AcceptUnixClient ();
340 } catch (SocketException) {
341 // If the listener is stopped while we
342 // wait for a connection, a
343 // SocketException is thrown.
344 break;
347 // FIXME: This is a hack to work around a mono
348 // bug. See the FIXMEs in UnixListener.cs for
349 // more info, but client should never be null,
350 // because AcceptUnixClient() should be
351 // throwing a SocketException when the
352 // listener is shut down. So when that is
353 // fixed, remove the if conditional.
355 // If client is null, the socket timed out.
356 if (client != null) {
357 ConnectionHandler handler = new ConnectionHandler (client);
358 lock (live_handlers)
359 live_handlers [handler] = handler;
360 ExceptionHandlingThread.Start (new ThreadStart (handler.HandleConnection));
364 Shutdown.WorkerFinished (this);
366 Logger.Log.Debug ("Server '{0}' shut down", this.socket_path);
369 public void Start ()
371 ExceptionHandlingThread.Start (new ThreadStart (this.Run));
374 public void Stop ()
376 this.running = false;
377 this.listener.Stop ();
378 File.Delete (this.socket_path);
381 //////////////////////////////////////////////////////////////////////////////
384 // Code to dispatch requests to the correct RequestMessageExecutor.
387 public delegate ResponseMessage RequestMessageHandler (RequestMessage msg);
389 // A simple wrapper class to turn a RequestMessageHandler delegate into
390 // a RequestMessageExecutor.
391 private class SimpleRequestMessageExecutor : RequestMessageExecutor {
393 RequestMessageHandler handler;
395 public SimpleRequestMessageExecutor (RequestMessageHandler handler)
397 this.handler = handler;
400 public override ResponseMessage Execute (RequestMessage req)
402 return this.handler (req);
406 static private Hashtable scanned_assemblies = new Hashtable ();
407 static private Hashtable request_type_to_handler = new Hashtable ();
408 static private Hashtable request_type_to_executor_type = new Hashtable ();
410 static public void RegisterRequestMessageHandler (Type request_type, RequestMessageHandler handler)
412 request_type_to_handler [request_type] = handler;
415 static public void ScanAssemblyForExecutors (Assembly assembly)
417 if (scanned_assemblies.Contains (assembly))
418 return;
419 scanned_assemblies [assembly] = assembly;
421 foreach (Type t in assembly.GetTypes ()) {
423 if (!t.IsSubclassOf (typeof (RequestMessageExecutor)))
424 continue;
426 // Yes, we know it doesn't have a RequestMessageAttribute
427 if (t == typeof (SimpleRequestMessageExecutor))
428 continue;
430 Attribute attr = Attribute.GetCustomAttribute (t, typeof (RequestMessageAttribute));
432 if (attr == null) {
433 Logger.Log.Warn ("No handler attribute for executor {0}", t);
434 continue;
437 RequestMessageAttribute pra = (RequestMessageAttribute) attr;
439 request_type_to_executor_type [pra.MessageType] = t;
443 static internal RequestMessageExecutor GetExecutor (RequestMessage req)
445 Type req_type = req.GetType ();
447 RequestMessageExecutor exec = null;
449 RequestMessageHandler handler;
450 handler = request_type_to_handler [req_type] as RequestMessageHandler;
452 if (handler != null) {
453 exec = new SimpleRequestMessageExecutor (handler);
454 } else {
455 Type t = request_type_to_executor_type [req_type] as Type;
456 if (t != null)
457 exec = (RequestMessageExecutor) Activator.CreateInstance (t);
460 return exec;