// // Mono.WebServer.BaseRequestBroker // // Authors: // Gonzalo Paniagua Javier (gonzalo@ximian.com) // Lluis Sanchez Gual (lluis@ximian.com) // // (C) Copyright 2004-2010 Novell, Inc // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; namespace Mono.WebServer { public class BaseRequestBroker: MarshalByRefObject, IRequestBroker { public event UnregisterRequestEventHandler UnregisterRequestEvent; // Contains the initial request capacity of a BaseRequestBroker const int INITIAL_REQUESTS = 200; // The size of a request buffer in bytes. // // This number should be equal to INPUT_BUFFER_SIZE // in System.Web.HttpRequest. const int BUFFER_SIZE = 32*1024; // Contains a lock to use when accessing and modifying the // request allocation tables. static readonly object reqlock = new object(); // Contains the request ID's. int[] request_ids = new int [INITIAL_REQUESTS]; // Contains the registered workers. Worker[] requests = new Worker [INITIAL_REQUESTS]; // Contains buffers for the requests to use. byte[][] buffers = new byte [INITIAL_REQUESTS][]; // Contains the number of active requests. int requests_count; // Contains the total number of requests served so far. // May freely wrap around. uint requests_served; /// /// Grows the size of the request allocation tables by 33%. /// /// This *MUST* be called with the reqlock held! /// /// ID to use for a new request. /// Current length of the allocation tables. int GrowRequests (ref int curlen) { int newsize = curlen + curlen/3; var new_request_ids = new int [newsize]; var new_requests = new Worker [newsize]; var new_buffers = new byte [newsize][]; request_ids.CopyTo (new_request_ids, 0); Array.Clear (request_ids, 0, request_ids.Length); request_ids = new_request_ids; requests.CopyTo (new_requests, 0); Array.Clear (requests, 0, requests.Length); requests = new_requests; buffers.CopyTo (new_buffers, 0); Array.Clear (buffers, 0, buffers.Length); buffers = new_buffers; curlen = newsize; return curlen + 1; } /// /// Gets the next available request ID, expanding the array /// of possible ID's if necessary. /// /// This *MUST* be called with the reqlock held! /// /// ID of the request. int GetNextRequestId () { int reqlen = request_ids.Length; requests_served++; // increment to 1 before putting into request_ids // so that the 0 id is reserved for slot not used if (requests_served == 0x8000) // and check for wrap-around for the above requests_served = 1; // making sure we don't exceed 0x7FFF or go negative requests_count++; int newid; if (requests_count >= reqlen) newid = GrowRequests (ref reqlen); else newid = Array.IndexOf (request_ids, 0); if (newid == -1) { // Should never happen... throw new ApplicationException ("could not allocate new request id"); } // TODO: newid had better not exceed 0xFFFF. newid = ((ushort)newid & 0xFFFF) | (((ushort)requests_served & 0x7FFF) << 16); request_ids [IdToIndex(newid)] = newid; return newid; } public int RegisterRequest (Worker worker) { int result; lock (reqlock) { result = IdToIndex (GetNextRequestId ()); requests [result] = worker; // Don't create a new array if one already exists. byte[] a = buffers [result]; if (a == null) buffers [result] = new byte [BUFFER_SIZE]; } return request_ids [result]; } int IdToIndex(int requestId) { return requestId & 0xFFFF; } public void UnregisterRequest (int id) { lock (reqlock) { if (!ValidRequest (id)) return; DoUnregisterRequest (id); int idx = IdToIndex (id); byte[] a = buffers [idx]; if (a != null) Array.Clear (a, 0, a.Length); requests [idx] = null; request_ids [idx] = 0; requests_count--; } } /// /// Invokes registered handlers of UnregisterRequestEvent. Each handler is passed an /// arguments object which contains the ID of a request that is about to be /// unregistered. /// /// ID of a request that is about to be unregistered. void DoUnregisterRequest (int id) { if (UnregisterRequestEvent == null) return; Delegate[] handlers = UnregisterRequestEvent.GetInvocationList (); if (handlers == null || handlers.Length == 0) return; var args = new UnregisterRequestEventArgs (id); foreach (UnregisterRequestEventHandler handler in handlers) handler (this, args); } protected bool ValidRequest (int requestId) { int idx = IdToIndex (requestId); return (idx >= 0 && idx < request_ids.Length && request_ids [idx] == requestId && buffers [idx] != null); } public int Read (int requestId, int size, out byte[] buffer) { buffer = null; Worker w; lock (reqlock) { if (!ValidRequest (requestId)) return 0; w = GetWorker (requestId); if (w == null) return 0; // Use a pre-allocated buffer only when the size matches // as it will be transferred across appdomain boundaries // in full length if (size == BUFFER_SIZE) { buffer = buffers [IdToIndex (requestId)]; } else { buffer = new byte[size]; } } return w.Read (buffer, 0, size); } public Worker GetWorker (int requestId) { lock (reqlock) { if (!ValidRequest (requestId)) return null; return requests [IdToIndex (requestId)]; } } public void Write (int requestId, byte[] buffer, int position, int size) { Worker worker = GetWorker (requestId); if (worker != null) worker.Write (buffer, position, size); } public void Close (int requestId) { Worker worker = GetWorker (requestId); if (worker != null) worker.Close (); } public void Flush (int requestId) { Worker worker = GetWorker (requestId); if (worker != null) worker.Flush (); } public bool IsConnected (int requestId) { Worker worker = GetWorker (requestId); return (worker != null && worker.IsConnected ()); } public override object InitializeLifetimeService () { return null; } } }