Add --enable-deletion option to buildindex. If used, buildindex will remove deleted...
[beagle.git] / beagled / Server.cs
blobaf5c1ac301063a5780a3debb1d2bb1b5eaa23240
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 public static XmlSerializer serializer = null;
45 private object client_lock = new object ();
46 private object blocking_read_lock = new object ();
48 private UnixClient client;
49 private RequestMessageExecutor executor = null; // Only set in the keepalive case
50 private Thread thread;
51 private bool in_blocking_read;
53 public ConnectionHandler (UnixClient client)
55 this.client = client;
58 public bool SendResponse (ResponseMessage response)
60 lock (this.client_lock) {
61 if (this.client == null)
62 return false;
64 try {
65 #if ENABLE_XML_DUMP
66 MemoryStream mem_stream = new MemoryStream ();
67 XmlFu.SerializeUtf8 (serializer, mem_stream, new ResponseWrapper (response));
68 mem_stream.Seek (0, SeekOrigin.Begin);
69 StreamReader r = new StreamReader (mem_stream);
70 Logger.Log.Debug ("Sending response:\n{0}\n", r.ReadToEnd ());
71 mem_stream.Seek (0, SeekOrigin.Begin);
72 mem_stream.WriteTo (this.client.GetStream ());
73 mem_stream.Close ();
74 #else
75 XmlFu.SerializeUtf8 (serializer, this.client.GetStream (), new ResponseWrapper (response));
76 #endif
77 // Send an end of message marker
78 this.client.GetStream ().WriteByte (0xff);
79 this.client.GetStream ().Flush ();
80 } catch (Exception e) {
81 Logger.Log.Debug ("Caught an exception sending response; socket shut down: {0}", e.Message);
82 return false;
85 return true;
89 public void CancelIfBlocking ()
91 // Work around some crappy .net behavior. We can't
92 // close a socket and have it exit out of a blocking
93 // read, so we have to abort the thread it's blocking
94 // in.
95 lock (this.blocking_read_lock) {
96 if (this.in_blocking_read) {
97 this.thread.Abort ();
102 public void Close ()
104 CancelIfBlocking ();
106 // It's important that we abort the thread before we
107 // grab the lock here and close the underlying
108 // UnixClient, or else we'd deadlock between here and
109 // the Read() in HandleConnection()
110 lock (this.client_lock) {
111 if (this.client != null) {
112 this.client.Close ();
113 this.client = null;
117 if (this.executor != null) {
118 this.executor.Cleanup ();
119 this.executor.AsyncResponseEvent -= OnAsyncResponse;
123 public void WatchCallback (IAsyncResult ar)
125 int bytes_read = 0;
127 try {
128 bytes_read = this.client.GetStream ().EndRead (ar);
129 } catch (SocketException) {
130 } catch (IOException) { }
132 if (bytes_read == 0)
133 Close ();
134 else
135 SetupWatch ();
138 private void SetupWatch ()
140 if (this.client == null) {
141 this.Close ();
142 return;
145 this.client.GetStream ().BeginRead (new byte[1024], 0, 1024,
146 new AsyncCallback (WatchCallback), null);
149 private void OnAsyncResponse (ResponseMessage response)
151 if (!SendResponse (response))
152 Close ();
155 static XmlSerializer req_serializer = new XmlSerializer (typeof (RequestWrapper), RequestMessage.Types);
157 public void HandleConnection ()
159 this.thread = Thread.CurrentThread;
161 RequestMessage req = null;
162 ResponseMessage resp = null;
164 bool force_close_connection = false;
166 // Read the data off the socket and store it in a
167 // temporary memory buffer. Once the end-of-message
168 // character has been read, discard remaining data
169 // and deserialize the request.
170 byte[] network_data = new byte [4096];
171 MemoryStream buffer_stream = new MemoryStream ();
172 int bytes_read, total_bytes = 0, end_index = -1;
174 // We use the network_data array as an object to represent this worker.
175 Shutdown.WorkerStart (network_data, String.Format ("HandleConnection ({0})", ++connection_count));
177 do {
178 bytes_read = 0;
180 try {
181 lock (this.blocking_read_lock)
182 this.in_blocking_read = true;
184 lock (this.client_lock) {
185 // The connection may have been closed within this loop.
186 if (this.client != null)
187 bytes_read = this.client.GetStream ().Read (network_data, 0, 4096);
190 lock (this.blocking_read_lock)
191 this.in_blocking_read = false;
192 } catch (Exception e) {
193 // Aborting the thread mid-read will
194 // cause an IOException to be thorwn,
195 // which sets the ThreadAbortException
196 // as its InnerException.
197 if (!(e is IOException || e is ThreadAbortException))
198 throw;
200 Logger.Log.Debug ("Bailing out of HandleConnection -- shutdown requested");
201 Server.MarkHandlerAsKilled (this);
202 Shutdown.WorkerFinished (network_data);
203 return;
206 total_bytes += bytes_read;
208 if (bytes_read > 0) {
209 // 0xff signifies end of message
210 end_index = ArrayFu.IndexOfByte (network_data, (byte) 0xff);
212 buffer_stream.Write (network_data, 0,
213 end_index == -1 ? bytes_read : end_index);
215 } while (bytes_read > 0 && end_index == -1);
217 // Something just connected to our socket and then
218 // hung up. The IndexHelper (among other things) does
219 // this to check that a server is still running. It's
220 // no big deal, so just clean up and close without
221 // running any handlers.
222 if (total_bytes == 0) {
223 force_close_connection = true;
224 goto cleanup;
227 buffer_stream.Seek (0, SeekOrigin.Begin);
229 #if ENABLE_XML_DUMP
230 StreamReader r = new StreamReader (buffer_stream);
231 Logger.Log.Debug ("Received request:\n{0}\n", r.ReadToEnd ());
232 buffer_stream.Seek (0, SeekOrigin.Begin);
233 #endif
235 try {
236 RequestWrapper wrapper = (RequestWrapper) req_serializer.Deserialize (buffer_stream);
238 req = wrapper.Message;
239 } catch (Exception e) {
240 resp = new ErrorResponse (e);
241 force_close_connection = true;
244 // If XmlSerializer can't deserialize the payload, we
245 // may get a null payload and not an exception. Or
246 // maybe the client just didn't send one.
247 if (req == null && resp == null) {
248 resp = new ErrorResponse ("Missing payload");
249 force_close_connection = true;
252 // And if there are no errors, execute the command
253 if (resp == null) {
255 RequestMessageExecutor exec;
256 exec = Server.GetExecutor (req);
258 if (exec == null) {
259 resp = new ErrorResponse (String.Format ("No handler available for {0}", req.GetType ()));
260 force_close_connection = true;
261 } else if (req.Keepalive) {
262 this.executor = exec;
263 exec.AsyncResponseEvent += OnAsyncResponse;
266 if (exec != null)
267 resp = exec.Execute (req);
270 // It's okay if the response is null; this means
271 // that keepalive is set and that we'll be sending
272 // back responses asynchronously. First, enforce
273 // that the response is not null if keepalive isn't
274 // set.
275 if (resp == null && !req.Keepalive)
276 resp = new ErrorResponse ("No response available, but keepalive is not set");
278 if (resp != null) {
279 //Logger.Log.Debug ("Sending response of type {0}", resp.GetType ());
280 if (!this.SendResponse (resp))
281 force_close_connection = true;
284 cleanup:
285 buffer_stream.Close ();
287 if (force_close_connection || !req.Keepalive)
288 Close ();
289 else
290 SetupWatch ();
292 Server.MarkHandlerAsKilled (this);
293 Shutdown.WorkerFinished (network_data);
297 public class Server {
299 private string socket_path;
300 private UnixListener listener;
301 private static Hashtable live_handlers = new Hashtable ();
302 private bool running = false;
304 public Server (string name)
306 ScanAssemblyForExecutors (Assembly.GetCallingAssembly ());
308 // Use the default name when passed null
309 if (name == null)
310 name = "socket";
312 this.socket_path = Path.Combine (PathFinder.GetRemoteStorageDir (true), name);
313 this.listener = new UnixListener (this.socket_path);
316 public Server () : this (null)
321 static Server ()
323 ScanAssemblyForExecutors (Assembly.GetExecutingAssembly ());
325 Shutdown.ShutdownEvent += OnShutdown;
328 static internal void MarkHandlerAsKilled (ConnectionHandler handler)
330 lock (live_handlers) {
331 live_handlers.Remove (handler);
335 private static void OnShutdown ()
337 lock (live_handlers) {
338 foreach (ConnectionHandler handler in live_handlers.Values) {
339 Logger.Log.Debug ("CancelIfBlocking {0}", handler);
340 handler.CancelIfBlocking ();
345 private void Run ()
347 this.listener.Start ();
348 this.running = true;
350 Shutdown.WorkerStart (this, String.Format ("server '{0}'", socket_path));
351 if (ConnectionHandler.serializer == null)
352 ConnectionHandler.serializer = new XmlSerializer (typeof (ResponseWrapper), ResponseMessage.Types);
354 while (this.running) {
355 UnixClient client;
356 try {
357 // This will block for an incoming connection.
358 // FIXME: But not really, it'll only wait a second.
359 // see the FIXME in UnixListener for more info.
360 client = this.listener.AcceptUnixClient ();
361 } catch (SocketException) {
362 // If the listener is stopped while we
363 // wait for a connection, a
364 // SocketException is thrown.
365 break;
368 // FIXME: This is a hack to work around a mono
369 // bug. See the FIXMEs in UnixListener.cs for
370 // more info, but client should never be null,
371 // because AcceptUnixClient() should be
372 // throwing a SocketException when the
373 // listener is shut down. So when that is
374 // fixed, remove the if conditional.
376 // If client is null, the socket timed out.
377 if (client != null) {
378 ConnectionHandler handler = new ConnectionHandler (client);
379 lock (live_handlers)
380 live_handlers [handler] = handler;
381 ExceptionHandlingThread.Start (new ThreadStart (handler.HandleConnection));
385 Shutdown.WorkerFinished (this);
387 Logger.Log.Debug ("Server '{0}' shut down", this.socket_path);
390 public void Start ()
392 if (!Shutdown.ShutdownRequested)
393 ExceptionHandlingThread.Start (new ThreadStart (this.Run));
396 public void Stop ()
398 if (this.running) {
399 this.running = false;
400 this.listener.Stop ();
402 File.Delete (this.socket_path);
405 //////////////////////////////////////////////////////////////////////////////
408 // Code to dispatch requests to the correct RequestMessageExecutor.
411 public delegate ResponseMessage RequestMessageHandler (RequestMessage msg);
413 // A simple wrapper class to turn a RequestMessageHandler delegate into
414 // a RequestMessageExecutor.
415 private class SimpleRequestMessageExecutor : RequestMessageExecutor {
417 RequestMessageHandler handler;
419 public SimpleRequestMessageExecutor (RequestMessageHandler handler)
421 this.handler = handler;
424 public override ResponseMessage Execute (RequestMessage req)
426 return this.handler (req);
430 static private Hashtable scanned_assemblies = new Hashtable ();
431 static private Hashtable request_type_to_handler = new Hashtable ();
432 static private Hashtable request_type_to_executor_type = new Hashtable ();
434 static public void RegisterRequestMessageHandler (Type request_type, RequestMessageHandler handler)
436 request_type_to_handler [request_type] = handler;
439 static public void ScanAssemblyForExecutors (Assembly assembly)
441 if (scanned_assemblies.Contains (assembly))
442 return;
443 scanned_assemblies [assembly] = assembly;
445 foreach (Type t in assembly.GetTypes ()) {
447 if (!t.IsSubclassOf (typeof (RequestMessageExecutor)))
448 continue;
450 // Yes, we know it doesn't have a RequestMessageAttribute
451 if (t == typeof (SimpleRequestMessageExecutor))
452 continue;
454 Attribute attr = Attribute.GetCustomAttribute (t, typeof (RequestMessageAttribute));
456 if (attr == null) {
457 Logger.Log.Warn ("No handler attribute for executor {0}", t);
458 continue;
461 RequestMessageAttribute pra = (RequestMessageAttribute) attr;
463 request_type_to_executor_type [pra.MessageType] = t;
467 static internal RequestMessageExecutor GetExecutor (RequestMessage req)
469 Type req_type = req.GetType ();
471 RequestMessageExecutor exec = null;
473 RequestMessageHandler handler;
474 handler = request_type_to_handler [req_type] as RequestMessageHandler;
476 if (handler != null) {
477 exec = new SimpleRequestMessageExecutor (handler);
478 } else {
479 Type t = request_type_to_executor_type [req_type] as Type;
480 if (t != null)
481 exec = (RequestMessageExecutor) Activator.CreateInstance (t);
484 return exec;