1 // 2 // Mono.WebServer.BaseRequestBroker 3 // 4 // Authors: 5 // Gonzalo Paniagua Javier (gonzalo@ximian.com) 6 // Lluis Sanchez Gual (lluis@ximian.com) 7 // 8 // (C) Copyright 2004-2010 Novell, Inc 9 // 10 // Permission is hereby granted, free of charge, to any person obtaining 11 // a copy of this software and associated documentation files (the 12 // "Software"), to deal in the Software without restriction, including 13 // without limitation the rights to use, copy, modify, merge, publish, 14 // distribute, sublicense, and/or sell copies of the Software, and to 15 // permit persons to whom the Software is furnished to do so, subject to 16 // the following conditions: 17 // 18 // The above copyright notice and this permission notice shall be 19 // included in all copies or substantial portions of the Software. 20 // 21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 // 29 30 31 using System; 32 33 namespace Mono.WebServer 34 { 35 public class BaseRequestBroker: MarshalByRefObject, IRequestBroker 36 { 37 public event UnregisterRequestEventHandler UnregisterRequestEvent; 38 39 // Contains the initial request capacity of a BaseRequestBroker 40 const int INITIAL_REQUESTS = 200; 41 42 // The size of a request buffer in bytes. 43 // 44 // This number should be equal to INPUT_BUFFER_SIZE 45 // in System.Web.HttpRequest. 46 const int BUFFER_SIZE = 32*1024; 47 48 // Contains a lock to use when accessing and modifying the 49 // request allocation tables. 50 static readonly object reqlock = new object(); 51 52 // Contains the request ID's. 53 int[] request_ids = new int [INITIAL_REQUESTS]; 54 55 // Contains the registered workers. 56 Worker[] requests = new Worker [INITIAL_REQUESTS]; 57 58 // Contains buffers for the requests to use. 59 byte[][] buffers = new byte [INITIAL_REQUESTS][]; 60 61 // Contains the number of active requests. 62 int requests_count; 63 64 // Contains the total number of requests served so far. 65 // May freely wrap around. 66 uint requests_served; 67 68 /// <summary> 69 /// Grows the size of the request allocation tables by 33%. 70 /// 71 /// This *MUST* be called with the reqlock held! 72 /// </summary> 73 /// <returns>ID to use for a new request.</returns> 74 /// <param name="curlen">Current length of the allocation tables.</param> GrowRequests(ref int curlen)75 int GrowRequests (ref int curlen) 76 { 77 int newsize = curlen + curlen/3; 78 var new_request_ids = new int [newsize]; 79 var new_requests = new Worker [newsize]; 80 var new_buffers = new byte [newsize][]; 81 82 request_ids.CopyTo (new_request_ids, 0); 83 Array.Clear (request_ids, 0, request_ids.Length); 84 request_ids = new_request_ids; 85 86 requests.CopyTo (new_requests, 0); 87 Array.Clear (requests, 0, requests.Length); 88 requests = new_requests; 89 90 buffers.CopyTo (new_buffers, 0); 91 Array.Clear (buffers, 0, buffers.Length); 92 buffers = new_buffers; 93 94 curlen = newsize; 95 return curlen + 1; 96 } 97 98 /// <summary> 99 /// Gets the next available request ID, expanding the array 100 /// of possible ID's if necessary. 101 /// 102 /// This *MUST* be called with the reqlock held! 103 /// </summary> 104 /// <returns>ID of the request.</returns> GetNextRequestId()105 int GetNextRequestId () 106 { 107 int reqlen = request_ids.Length; 108 109 requests_served++; // increment to 1 before putting into request_ids 110 // so that the 0 id is reserved for slot not used 111 if (requests_served == 0x8000) // and check for wrap-around for the above 112 requests_served = 1; // making sure we don't exceed 0x7FFF or go negative 113 114 requests_count++; 115 116 int newid; 117 if (requests_count >= reqlen) 118 newid = GrowRequests (ref reqlen); 119 else 120 newid = Array.IndexOf (request_ids, 0); 121 122 if (newid == -1) { 123 // Should never happen... 124 throw new ApplicationException ("could not allocate new request id"); 125 } 126 127 // TODO: newid had better not exceed 0xFFFF. 128 newid = ((ushort)newid & 0xFFFF) | (((ushort)requests_served & 0x7FFF) << 16); 129 request_ids [IdToIndex(newid)] = newid; 130 return newid; 131 } 132 RegisterRequest(Worker worker)133 public int RegisterRequest (Worker worker) 134 { 135 int result; 136 137 lock (reqlock) { 138 result = IdToIndex (GetNextRequestId ()); 139 requests [result] = worker; 140 141 // Don't create a new array if one already exists. 142 byte[] a = buffers [result]; 143 if (a == null) 144 buffers [result] = new byte [BUFFER_SIZE]; 145 } 146 147 return request_ids [result]; 148 } 149 IdToIndex(int requestId)150 int IdToIndex(int requestId) { 151 return requestId & 0xFFFF; 152 } 153 UnregisterRequest(int id)154 public void UnregisterRequest (int id) 155 { 156 lock (reqlock) { 157 if (!ValidRequest (id)) 158 return; 159 160 DoUnregisterRequest (id); 161 int idx = IdToIndex (id); 162 163 byte[] a = buffers [idx]; 164 if (a != null) 165 Array.Clear (a, 0, a.Length); 166 requests [idx] = null; 167 request_ids [idx] = 0; 168 requests_count--; 169 } 170 } 171 172 /// <summary> 173 /// Invokes registered handlers of UnregisterRequestEvent. Each handler is passed an 174 /// arguments object which contains the ID of a request that is about to be 175 /// unregistered. 176 /// </summary> 177 /// <param name="id">ID of a request that is about to be unregistered.</param> DoUnregisterRequest(int id)178 void DoUnregisterRequest (int id) 179 { 180 if (UnregisterRequestEvent == null) 181 return; 182 Delegate[] handlers = UnregisterRequestEvent.GetInvocationList (); 183 if (handlers == null || handlers.Length == 0) 184 return; 185 186 var args = new UnregisterRequestEventArgs (id); 187 foreach (UnregisterRequestEventHandler handler in handlers) 188 handler (this, args); 189 } 190 ValidRequest(int requestId)191 protected bool ValidRequest (int requestId) 192 { 193 int idx = IdToIndex (requestId); 194 return (idx >= 0 && idx < request_ids.Length && request_ids [idx] == requestId && 195 buffers [idx] != null); 196 } 197 Read(int requestId, int size, out byte[] buffer)198 public int Read (int requestId, int size, out byte[] buffer) 199 { 200 buffer = null; 201 202 Worker w; 203 204 lock (reqlock) { 205 if (!ValidRequest (requestId)) 206 return 0; 207 208 w = GetWorker (requestId); 209 if (w == null) 210 return 0; 211 212 // Use a pre-allocated buffer only when the size matches 213 // as it will be transferred across appdomain boundaries 214 // in full length 215 if (size == BUFFER_SIZE) { 216 buffer = buffers [IdToIndex (requestId)]; 217 } else { 218 buffer = new byte[size]; 219 } 220 } 221 222 return w.Read (buffer, 0, size); 223 } 224 GetWorker(int requestId)225 public Worker GetWorker (int requestId) 226 { 227 lock (reqlock) { 228 if (!ValidRequest (requestId)) 229 return null; 230 231 return requests [IdToIndex (requestId)]; 232 } 233 } 234 Write(int requestId, byte[] buffer, int position, int size)235 public void Write (int requestId, byte[] buffer, int position, int size) 236 { 237 Worker worker = GetWorker (requestId); 238 if (worker != null) 239 worker.Write (buffer, position, size); 240 } 241 Close(int requestId)242 public void Close (int requestId) 243 { 244 Worker worker = GetWorker (requestId); 245 if (worker != null) 246 worker.Close (); 247 } 248 Flush(int requestId)249 public void Flush (int requestId) 250 { 251 Worker worker = GetWorker (requestId); 252 if (worker != null) 253 worker.Flush (); 254 } 255 IsConnected(int requestId)256 public bool IsConnected (int requestId) 257 { 258 Worker worker = GetWorker (requestId); 259 260 return (worker != null && worker.IsConnected ()); 261 } 262 InitializeLifetimeService()263 public override object InitializeLifetimeService () 264 { 265 return null; 266 } 267 } 268 } 269