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