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
|
// <copyright file="ChangeMonitor.cs" company="Microsoft">
// Copyright (c) 2009 Microsoft Corporation. All rights reserved.
// </copyright>
using System;
using System.Runtime.Caching.Resources;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
// Every member of this class is thread-safe.
//
// Derived classes begin monitoring during construction, so that a user can know if the
// dependency changed any time after construction. For example, suppose we have a
// FileChangeMonitor class that derives from ChangeMonitor. A user might create an instance
// of FileChangeMonitor for an XML file, and then read the file to populate an object representation.
// The user would then cache the object with the FileChangeMonitor. The user could optionally check the
// HasChanged property of the FileChangeMonitor, to see if the XML file changed while the object
// was being populated, and if it had changed, they could call Dispose and start over, without
// inserting the item into the cache. However, in a multi-threaded environment, for cleaner, easier
// to maintain code, it's usually appropriate to just insert without checking HasChanged, since the
// cache implementer will handle this for you, and the next thread to attempt to get the object
// will recreate and insert it.
//
// The following contract must be followed by derived classes, cache implementers, and users of the
// derived class:
//
// 1. The constructor of a derived class must set UniqueId, begin monitoring for dependency
// changes, and call InitializationComplete before returning. If a dependency changes
// before initialization is complete, for example, if a dependent cache key is not found
// in the cache, the constructor must invoke OnChanged. The constructor can only call
// Dispose after InitializationComplete is called, because Dispose will throw
// InvalidOperationException if initialization is not complete.
// 2. Once constructed, the user must either insert the ChangeMonitor into an ObjectCache, or
// if they're not going to use it, they must call Dispose.
// 3. Once inserted into an ObjectCache, the ObjectCache implementation must ensure that the
// ChangeMonitor is eventually disposed. Even if the insert is invalid, and results in an
// exception being thrown, the ObjectCache implementation must call Dispose. If this we're not
// a requirement, users of the ChangeMonitor would need exception handling around each insert
// into the cache that carefully ensures the dependency is disposed. While this would work, we
// think it is better to put this burden on the ObjectCache implementer, since users are far more
// numerous than cache implementers.
// 4. After the ChangeMonitor is inserted into a cache, the ObjectCache implementer must call
// NotifyOnChanged, passing in an OnChangedCallback. NotifyOnChanged can only be called once,
// and will throw InvalidOperationException on subsequent calls. If the dependency has already
// changed, the OnChangedCallback will be called when NotifyOnChanged is called. Otherwise, the
// OnChangedCallback will be called exactly once, when OnChanged is invoked or when Dispose
// is invoked, which ever happens first.
// 5. The OnChangedCallback provided by the cache implementer should remove the cache entry, and specify
// a reason of CacheEntryRemovedReason.DependencyChanged. Care should be taken to remove the specific
// entry having this dependency, and not it's replacement, which will have the same key.
// 6. In general, it is okay for OnChanged to be called at any time. If OnChanged is called before
// NotifyOnChanged is called, the "state" from the original call to OnChanged will be saved, and the
// callback to NotifyOnChange will be called immediately when NotifyOnChanged is invoked.
// 7. A derived class must implement Dispose(bool disposing) to release all managed and unmanaged
// resources when "disposing" is true. Dispose(true) is only called once, when the instance is
// disposed. The derived class must not call Dispose(true) directly--it should only be called by
// the ChangeMonitor class, when disposed. Although a derived class could implement a finalizer and
// invoke Dispose(false), this is generally not necessary. Dependency monitoring is typically performed
// by a service that maintains a reference to the ChangeMonitor, preventing it from being garbage collected,
// and making finalizers useless. To help prevent leaks, when a dependency changes, OnChanged disposes
// the ChangeMonitor, unless initialization has not yet completed.
// 8. Dispose() must be called, and is designed to be called, in one of the following three ways:
// - The user must call Dispose() if they decide not to insert the ChangeMonitor into a cache. Otherwise,
// the ChangeMonitor will continue monitoring for changes and be unavailable for garbage collection.
// - The cache implementor is responsible for calling Dispose() once an attempt is made to insert it.
// Even if the insert throws, the cache implementor must dispose the dependency.
// Even if the entry is removed, the cache implementor must dispose the dependency.
// - The OnChanged method will automatically call Dispose if initialization is complete. Otherwise, when
// the derived class' constructor calls InitializationComplete, the instance will be automatically disposed.
//
// Before inserted into the cache, the user must ensure the dependency is disposed. Once inserted into the
// cache, the cache implementer must ensure that Dispose is called, even if the insert fails. After being inserted
// into a cache, the user should not dispose the dependency. When Dispose is called, it is treated as if the dependency
// changed, and OnChanged is automatically invoked.
// 9. HasChanged will be true after OnChanged is called by the derived class, regardless of whether an OnChangedCallback has been set
// by a call to NotifyOnChanged.
namespace System.Runtime.Caching {
public abstract class ChangeMonitor : IDisposable {
private const int INITIALIZED = 0x01; // initialization complete
private const int CHANGED = 0x02; // dependency changed
private const int INVOKED = 0x04; // OnChangedCallback has been invoked
private const int DISPOSED = 0x08; // Dispose(true) called, or about to be called
private readonly static object NOT_SET = new object();
private SafeBitVector32 _flags;
private OnChangedCallback _onChangedCallback;
private Object _onChangedState = NOT_SET;
// The helper routines (OnChangedHelper and DisposeHelper) are used to prevent
// an infinite loop, where Dispose calls OnChanged and OnChanged calls Dispose.
[SuppressMessage("Microsoft.Performance", "CA1816:DisposeMethodsShouldCallSuppressFinalize", Justification = "Grandfathered suppression from original caching code checkin")]
private void DisposeHelper() {
// if not initialized, return without doing anything.
if (_flags[INITIALIZED]) {
if (_flags.ChangeValue(DISPOSED, true)) {
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
// The helper routines (OnChangedHelper and DisposeHelper) are used to prevent
// an infinite loop, where Dispose calls OnChanged and OnChanged calls Dispose.
private void OnChangedHelper(Object state) {
_flags[CHANGED] = true;
// the callback is only invoked once, after NotifyOnChanged is called, so
// remember "state" on the first call and use it when invoking the callback
Interlocked.CompareExchange(ref _onChangedState, state, NOT_SET);
OnChangedCallback onChangedCallback = _onChangedCallback;
if (onChangedCallback != null) {
// only invoke the callback once
if (_flags.ChangeValue(INVOKED, true)) {
onChangedCallback(_onChangedState);
}
}
}
//
// protected members
//
// Derived classes must implement this. When "disposing" is true,
// all managed and unmanaged resources are disposed and any references to this
// object are released so that the ChangeMonitor can be garbage collected.
// It is guaranteed that ChangeMonitor.Dispose() will only invoke
// Dispose(bool disposing) once.
protected abstract void Dispose(bool disposing);
// Derived classes must call InitializationComplete
protected void InitializationComplete() {
_flags[INITIALIZED] = true;
// If the dependency has already changed, or someone tried to dispose us, then call Dispose now.
Dbg.Assert(_flags[INITIALIZED], "It is critical that INITIALIZED is set before CHANGED is checked below");
if (_flags[CHANGED]) {
Dispose();
}
}
// Derived classes call OnChanged when the dependency changes. Optionally,
// they may pass state which will be passed to the OnChangedCallback. The
// OnChangedCallback is only invoked once, and only after NotifyOnChanged is
// called by the cache implementer. OnChanged is also invoked when the instance
// is disposed, but only has an affect if the callback has not already been invoked.
protected void OnChanged(Object state) {
OnChangedHelper(state);
// OnChanged will also invoke Dispose, but only after initialization is complete
Dbg.Assert(_flags[CHANGED], "It is critical that CHANGED is set before INITIALIZED is checked below.");
if (_flags[INITIALIZED]) {
DisposeHelper();
}
}
//
// public members
//
// set to true when the dependency changes, specifically, when OnChanged is called.
public bool HasChanged { get { return _flags[CHANGED]; } }
// set to true when this instance is disposed, specifically, after
// Dispose(bool disposing) is called by Dispose().
public bool IsDisposed { get { return _flags[DISPOSED]; } }
// a unique ID representing this ChangeMonitor, typically consisting of
// the dependency names and last-modified times.
public abstract string UniqueId { get; }
// Dispose must be called to release the ChangeMonitor. In order to
// prevent derived classes from overriding Dispose, it is not an explicit
// interface implementation.
//
// Before cache insertion, if the user decides not to do a cache insert, they
// must call this to dispose the dependency; otherwise, the ChangeMonitor will
// be referenced and unable to be garbage collected until the dependency changes.
//
// After cache insertion, the cache implementer must call this when the cache entry
// is removed, for whatever reason. Even if an exception is thrown during insert.
//
// After cache insertion, the user should not call Dispose. However, since there's
// no way to prevent this, doing so will invoke the OnChanged event handler, if it
// hasn't already been invoked, and the cache entry will be notified as if the
// dependency has changed.
//
// Dispose() will only invoke the Dispose(bool disposing) method of derived classes
// once, the first time it is called. Subsequent calls to Dispose() perform no
// operation. After Dispose is called, the IsDisposed property will be true.
[SuppressMessage("Microsoft.Performance", "CA1816:DisposeMethodsShouldCallSuppressFinalize", Justification = "Grandfathered suppression from original caching code checkin")]
public void Dispose() {
OnChangedHelper(null);
// If not initialized, throw, so the derived class understands that it must call InitializeComplete before Dispose.
Dbg.Assert(_flags[CHANGED], "It is critical that CHANGED is set before INITIALIZED is checked below.");
if (!_flags[INITIALIZED]) {
throw new InvalidOperationException(R.Init_not_complete);
}
DisposeHelper();
}
// Cache implementers must call this to be notified of any dependency changes.
// NotifyOnChanged can only be invoked once, and will throw InvalidOperationException
// on subsequent calls. The OnChangedCallback is guaranteed to be called exactly once.
// It will be called when the dependency changes, or if it has already changed, it will
// be called immediately (on the same thread??).
public void NotifyOnChanged(OnChangedCallback onChangedCallback) {
if (onChangedCallback == null) {
throw new ArgumentNullException("onChangedCallback");
}
if (Interlocked.CompareExchange(ref _onChangedCallback, onChangedCallback, null) != null) {
throw new InvalidOperationException(R.Method_already_invoked);
}
// if it already changed, raise the event now.
if (_flags[CHANGED]) {
OnChanged(null);
}
}
}
}
|