4 // Copyright (C) 2005 Novell, Inc.
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.
28 using System
.Collections
;
30 using System
.Net
.Sockets
;
31 using System
.Reflection
;
32 using System
.Threading
;
33 using System
.Xml
.Serialization
;
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
)
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)
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 ());
83 XmlFu
.SerializeUtf8 (resp_serializer
, this.client
.GetStream (), new ResponseWrapper (response
));
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 ());
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
103 lock (this.blocking_read_lock
) {
104 if (this.in_blocking_read
) {
105 this.thread
.Abort ();
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 ();
125 if (this.executor
!= null) {
126 this.executor
.Cleanup ();
127 this.executor
.AsyncResponseEvent
-= OnAsyncResponse
;
131 public void WatchCallback (IAsyncResult ar
)
136 bytes_read
= this.client
.GetStream ().EndRead (ar
);
137 } catch (SocketException
) {
138 } catch (IOException
) { }
146 private void SetupWatch ()
148 if (this.client
== null) {
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
))
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
));
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
))
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
);
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;
236 buffer_stream
.Seek (0, SeekOrigin
.Begin
);
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
);
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
);
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
271 RequestMessageExecutor exec
;
272 exec
= Server
.GetExecutor (req
);
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
;
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
297 if (resp
== null && !req
.Keepalive
)
298 resp
= new ErrorResponse ("No response available, but keepalive is not set");
301 //Logger.Log.Debug ("Sending response of type {0}", resp.GetType ());
302 if (!this.SendResponse (resp
))
303 force_close_connection
= true;
307 buffer_stream
.Close ();
309 if (force_close_connection
|| !req
.Keepalive
)
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;
330 foreach (Assembly assembly
in AppDomain
.CurrentDomain
.GetAssemblies ())
331 ScanAssemblyForExecutors (assembly
);
334 public Server (string name
)
336 // Use the default name when passed null
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 ();
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 ();
376 this.listener
.Start ();
379 if (! Shutdown
.WorkerStart (this, String
.Format ("server '{0}'", socket_path
)))
382 while (this.running
) {
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.
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
);
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
);
421 throw new Exception ("Server must be initialized before starting");
423 if (!Shutdown
.ShutdownRequested
)
424 ExceptionHandlingThread
.Start (new ThreadStart (this.Run
));
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
))
474 scanned_assemblies
[assembly
] = assembly
;
476 foreach (Type t
in ReflectionFu
.GetTypesFromAssemblyAttribute (assembly
, typeof (RequestMessageExecutorTypesAttribute
))) {
478 Attribute attr
= Attribute
.GetCustomAttribute (t
, typeof (RequestMessageAttribute
));
481 Logger
.Log
.Warn ("No handler attribute for executor {0}", t
);
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
);
503 Type t
= request_type_to_executor_type
[req_type
] as Type
;
505 exec
= (RequestMessageExecutor
) Activator
.CreateInstance (t
);