File: SuspendManager.cs

package info (click to toggle)
mono 6.8.0.105%2Bdfsg-3.3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,284,512 kB
  • sloc: cs: 11,172,132; xml: 2,850,069; ansic: 671,653; cpp: 122,091; perl: 59,366; javascript: 30,841; asm: 22,168; makefile: 20,093; sh: 15,020; python: 4,827; pascal: 925; sql: 859; sed: 16; php: 1
file content (162 lines) | stat: -rw-r--r-- 7,262 bytes parent folder | download | duplicates (6)
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
//------------------------------------------------------------------------------
// <copyright file="SuspendManager.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------

namespace System.Web.Hosting {
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Threading;
    using System.Web.Util;

    // Handles calling suspend and resume methods, including issues around
    // synchronization and timeout handling.

    internal sealed class SuspendManager {

        private static readonly TimeSpan _suspendMethodTimeout = TimeSpan.FromSeconds(5);

        private readonly ConcurrentDictionary<ISuspendibleRegisteredObject, object> _registeredObjects = new ConcurrentDictionary<ISuspendibleRegisteredObject, object>();

        public void RegisterObject(ISuspendibleRegisteredObject o) {
            Debug.Assert(o != null);
            _registeredObjects[o] = null;
        }

        public void UnregisterObject(ISuspendibleRegisteredObject o) {
            Debug.Assert(o != null);
            ((IDictionary<ISuspendibleRegisteredObject, object>)_registeredObjects).Remove(o);
        }

        // Returns a state object that will be passed to the Resume method.
        public object Suspend() {
            // ConcurrentDictionary.Count / IsEmpty are basically as expensive as getting
            // the entire collection of keys, so we may as well just read the keys anyway.
            var allRegisteredObjects = _registeredObjects.Keys;
            return SuspendImpl(allRegisteredObjects);
        }

        private static SuspendState SuspendImpl(ICollection<ISuspendibleRegisteredObject> allRegisteredObjects) {
            // Our behavior is:
            // - We'll call each registered object's suspend method serially.
            // - All methods have a combined 5 seconds to respond, at which
            //   point we'll forcibly return to our caller.
            // - If a Resume call comes in, we'll not call any Suspend methods
            //   we haven't yet gotten around to, and we'll execute each
            //   resume callback we got.
            // - Resume callbacks may fire in parallel, even if Suspend methods
            //   fire sequentially.
            // - Resume methods fire asynchronously, so other events (such as
            //   Stop or a new Suspend call) could happen while a Resume callback
            //   is in progress.

            CountdownEvent countdownEvent = new CountdownEvent(2);
            SuspendState suspendState = new SuspendState(allRegisteredObjects);

            // Unsafe QUWI since occurs outside the context of a request.
            // We are not concerned about impersonation, identity, etc.

            // Invoke any registered subscribers to let them know that we're about
            // to suspend. This is done in parallel with ASP.NET's own cleanup below.
            if (allRegisteredObjects.Count > 0) {
                ThreadPool.UnsafeQueueUserWorkItem(_ => {
                    suspendState.Suspend();
                    countdownEvent.Signal();
                }, null);
            }
            else {
                countdownEvent.Signal(); // nobody is subscribed
            }

            // Release any unnecessary memory that we're holding on to. The GC will
            // be able to reclaim these, which means that we'll have to page in less
            // memory when the next request comes in.
            ThreadPool.UnsafeQueueUserWorkItem(_ => {
                // Release any char[] buffers we're keeping around
                HttpWriter.ReleaseAllPooledBuffers();

                // Trim expired entries from the runtime cache
                var iCache = HttpRuntime.Cache.GetInternalCache(createIfDoesNotExist: false);
                var oCache = HttpRuntime.Cache.GetObjectCache(createIfDoesNotExist: false);
                if (iCache != null) {
                    iCache.Trim(0);
                }
                if (oCache != null && !oCache.Equals(iCache)) {
                    oCache.Trim(0);
                }

                // Trim all pooled HttpApplication instances
                HttpApplicationFactory.TrimApplicationInstances(removeAll: true);

                countdownEvent.Signal();
            }, null);

            if (Debug.IsDebuggerPresent()) {
                countdownEvent.Wait(); // to assist with debugging, don't time out if a debugger is attached
            }
            else {
                countdownEvent.Wait(_suspendMethodTimeout); // blocking call, ok for our needs since has finite wait time
            }
            return suspendState;
        }

        public void Resume(object state) {
            ((SuspendState)state).Resume();
        }

        internal sealed class SuspendState {
            private static readonly WaitCallback _quwiThunk = (state) => ((Action)state)();

            private readonly ICollection<ISuspendibleRegisteredObject> _suspendibleObjects;

            // these two fields should only ever be accessed under lock
            private readonly List<Action> _resumeCallbacks;
            private bool _resumeWasCalled;

            public SuspendState(ICollection<ISuspendibleRegisteredObject> suspendibleObjects) {
                _suspendibleObjects = suspendibleObjects;
                _resumeCallbacks = new List<Action>(suspendibleObjects.Count);
            }

            public void Suspend() {
                foreach (ISuspendibleRegisteredObject suspendibleObject in _suspendibleObjects) {
                    Action callback = suspendibleObject.Suspend();

                    lock (this) {
                        // If Resume was called while the Suspend method was still executing,
                        // this callback won't be invoked, so we need to invoke it manually.
                        if (_resumeWasCalled) {
                            if (callback != null) {
                                InvokeResumeCallbackAsync(callback);
                            }
                            return; // don't run any other Suspend methods
                        }

                        if (callback != null) {
                            _resumeCallbacks.Add(callback);
                        }
                    }
                }
            }

            public void Resume() {
                lock (this) {
                    Debug.Assert(!_resumeWasCalled, "Resume was called too many times!");
                    _resumeWasCalled = true;

                    foreach (Action callback in _resumeCallbacks) {
                        InvokeResumeCallbackAsync(callback);
                    }
                }
            }

            private static void InvokeResumeCallbackAsync(Action callback) {
                // Unsafe QUWI since occurs outside the context of a request.
                // We are not concerned about impersonation, identity, etc.
                ThreadPool.UnsafeQueueUserWorkItem(_quwiThunk, callback);
            }
        }
    }
}