// // Copyright (c) 2009 Microsoft Corporation. All rights reserved. // 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); } } } }