1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
|
//------------------------------------------------------------------------------
// <copyright file="RequestQueue.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
//
// Request Queue
// queues up the requests to avoid thread pool starvation,
// making sure that there are always available threads to process requests
//
namespace System.Web {
using System.Threading;
using System.Collections;
using System.Web.Util;
using System.Web.Hosting;
using System.Web.Configuration;
internal class RequestQueue {
// configuration params
private int _minExternFreeThreads;
private int _minLocalFreeThreads;
private int _queueLimit;
private TimeSpan _clientConnectedTime;
private bool _iis6;
// two queues -- one for local requests, one for external
private Queue _localQueue = new Queue();
private Queue _externQueue = new Queue();
// total count
private int _count;
// work items queued to pick up new work
private WaitCallback _workItemCallback;
private int _workItemCount;
private const int _workItemLimit = 2;
private bool _draining;
// timer to drain the queue
private readonly TimeSpan _timerPeriod = new TimeSpan(0, 0, 10); // 10 seconds
private Timer _timer;
// helpers
private static bool IsLocal(HttpWorkerRequest wr) {
String remoteAddress = wr.GetRemoteAddress();
// check if localhost
if (remoteAddress == "127.0.0.1" || remoteAddress == "::1")
return true;
// if unknown, assume not local
if (String.IsNullOrEmpty(remoteAddress))
return false;
// compare with local address
if (remoteAddress == wr.GetLocalAddress())
return true;
return false;
}
private void QueueRequest(HttpWorkerRequest wr, bool isLocal) {
lock (this) {
if (isLocal) {
_localQueue.Enqueue(wr);
}
else {
_externQueue.Enqueue(wr);
}
_count++;
}
PerfCounters.IncrementGlobalCounter(GlobalPerfCounter.REQUESTS_QUEUED);
PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_IN_APPLICATION_QUEUE);
if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_REQ_QUEUED, wr);
}
private HttpWorkerRequest DequeueRequest(bool localOnly) {
HttpWorkerRequest wr = null;
while (_count > 0) {
lock (this) {
if (_localQueue.Count > 0) {
wr = (HttpWorkerRequest)_localQueue.Dequeue();
_count--;
}
else if (!localOnly && _externQueue.Count > 0) {
wr = (HttpWorkerRequest)_externQueue.Dequeue();
_count--;
}
}
if (wr == null) {
break;
}
else {
PerfCounters.DecrementGlobalCounter(GlobalPerfCounter.REQUESTS_QUEUED);
PerfCounters.DecrementCounter(AppPerfCounter.REQUESTS_IN_APPLICATION_QUEUE);
if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Information, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_REQ_DEQUEUED, wr);
if (!CheckClientConnected(wr)) {
HttpRuntime.RejectRequestNow(wr, true);
wr = null;
PerfCounters.IncrementGlobalCounter(GlobalPerfCounter.REQUESTS_DISCONNECTED);
PerfCounters.IncrementCounter(AppPerfCounter.APP_REQUEST_DISCONNECTED);
}
else {
break;
}
}
}
return wr;
}
// This method will check to see if the client is still connected.
// The checks are only done if it's an in-proc Isapi request AND the request has been waiting
// more than the configured clientConenctedCheck time.
private bool CheckClientConnected(HttpWorkerRequest wr) {
if (DateTime.UtcNow - wr.GetStartTime() > _clientConnectedTime)
return wr.IsClientConnected();
else
return true;
}
// ctor
internal RequestQueue(int minExternFreeThreads, int minLocalFreeThreads, int queueLimit, TimeSpan clientConnectedTime) {
_minExternFreeThreads = minExternFreeThreads;
_minLocalFreeThreads = minLocalFreeThreads;
_queueLimit = queueLimit;
_clientConnectedTime = clientConnectedTime;
_workItemCallback = new WaitCallback(this.WorkItemCallback);
_timer = new Timer(new TimerCallback(this.TimerCompletionCallback), null, _timerPeriod, _timerPeriod);
_iis6 = HostingEnvironment.IsUnderIIS6Process;
// set the minimum number of requests that must be executing in order to detect a deadlock
int maxWorkerThreads, maxIoThreads;
ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxIoThreads);
UnsafeNativeMethods.SetMinRequestsExecutingToDetectDeadlock(maxWorkerThreads - minExternFreeThreads);
}
// method called from HttpRuntime for incoming requests
internal HttpWorkerRequest GetRequestToExecute(HttpWorkerRequest wr) {
int workerThreads, ioThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
int freeThreads;
if (_iis6)
freeThreads = workerThreads; // ignore IO threads to avoid starvation from Indigo TCP requests
else
freeThreads = (ioThreads > workerThreads) ? workerThreads : ioThreads;
// fast path when there are threads available and nothing queued
if (freeThreads >= _minExternFreeThreads && _count == 0)
return wr;
bool isLocal = IsLocal(wr);
// fast path when there are threads for local requests available and nothing queued
if (isLocal && freeThreads >= _minLocalFreeThreads && _count == 0)
return wr;
// reject if queue limit exceeded
if (_count >= _queueLimit) {
HttpRuntime.RejectRequestNow(wr, false);
return null;
}
// can't execute the current request on the current thread -- need to queue
QueueRequest(wr, isLocal);
// maybe can execute a request previously queued
if (freeThreads >= _minExternFreeThreads) {
wr = DequeueRequest(false); // enough threads to process even external requests
}
else if (freeThreads >= _minLocalFreeThreads) {
wr = DequeueRequest(true); // enough threads to process only local requests
}
else {
wr = null; // not enough threads -> do nothing on this thread
ScheduleMoreWorkIfNeeded(); // try to schedule to worker thread
}
return wr;
}
// method called from HttpRuntime at the end of request
internal void ScheduleMoreWorkIfNeeded() {
// too late for more work if draining
if (_draining)
return;
// is queue empty?
if (_count == 0)
return;
// already scheduled enough work items
if (_workItemCount >= _workItemLimit)
return;
// enough worker threads?
int workerThreads, ioThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
if (workerThreads < _minLocalFreeThreads)
return;
// queue the work item
Interlocked.Increment(ref _workItemCount);
ThreadPool.QueueUserWorkItem(_workItemCallback);
}
// is empty property
internal bool IsEmpty {
get { return (_count == 0); }
}
// method called to pick up more work
private void WorkItemCallback(Object state) {
Interlocked.Decrement(ref _workItemCount);
// too late for more work if draining
if (_draining)
return;
// is queue empty?
if (_count == 0)
return;
int workerThreads, ioThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
// not enough worker threads to do anything
if (workerThreads < _minLocalFreeThreads)
return;
// pick up request from the queue
HttpWorkerRequest wr = DequeueRequest(workerThreads < _minExternFreeThreads);
if (wr == null)
return;
// let another work item through before processing the request
ScheduleMoreWorkIfNeeded();
// call the runtime to process request
HttpRuntime.ProcessRequestNow(wr);
}
// periodic timer to pick up more work
private void TimerCompletionCallback(Object state) {
ScheduleMoreWorkIfNeeded();
}
// reject all requests
internal void Drain() {
// set flag before killing timer to shorten the code path
// in the callback after the timer is disposed
_draining = true;
// stop the timer
if (_timer != null) {
((IDisposable)_timer).Dispose();
_timer = null;
}
// wait for all work items to finish
while (_workItemCount > 0)
Thread.Sleep(100);
// is queue empty?
if (_count == 0)
return;
// reject the remaining requests
for (;;) {
HttpWorkerRequest wr = DequeueRequest(false);
if (wr == null)
break;
HttpRuntime.RejectRequestNow(wr, false);
}
}
}
}
|