|
//---------------------------------------------------------------------------
//
// <copyright file="ShutDownListener.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved.
// </copyright>
//
// Description: Listen for shut down events on behalf of a target, in a way that
// does not leak the target. ShutDown events include:
// AppDomain.DomainUnload
// AppDomain.ProcessExit
// Dispatcher.ShutdownFinished
// Listening to these events directly can cause leaks, since the AppDomain
// lives longer than the target.
//
// The WeakEvent pattern has similar goals, but can't be used here because
// the WeakEvent table itself needs to listen for shutdown events.
//
// "Target" refers to the actual consumer of the event(s). Each class
// XYZ that wants to consume these events should define a class
// XYZShutDownListener deriving from ShutDownListener that overrides the
// OnShutDown method. The target's constructor typically creates an
// instance of the XYZShutDownListener, which holds a weak reference to
// the target, and listens for the desired events. When an event occurs,
// the OnShutDown override is passed a (normal) reference to the target
// object, and typically calls an appropriate target method that reacts
// to the event. (See examples in WeakEventTable, DataBindEngine, etc.)
//
// Shutdown is a "one-time" process. When the ShutDownListener receives
// any one of the desired events, it stops listening to all events.
//---------------------------------------------------------------------------
using System;
using System.Security; // [SecurityCritical]
using System.Threading; // Interlocked
using System.Windows.Threading; // Dispatcher
using MS.Internal.WindowsBase; // [FriendAccessAllowed]
namespace MS.Internal
{
[FriendAccessAllowed] // defined in Base, also used in Framework
[Flags]
internal enum ShutDownEvents : ushort
{
DomainUnload = 0x0001,
ProcessExit = 0x0002,
DispatcherShutdown = 0x0004,
AppDomain = DomainUnload | ProcessExit,
All = AppDomain | DispatcherShutdown,
}
[FriendAccessAllowed] // defined in Base, also used in Framework
internal abstract class ShutDownListener : WeakReference
{
/// <SecurityNote>
/// Critical - accesses AppDomain.DomainUnload event.
/// not SecurityTreatAsSafe.
/// </SecurityNote>
[SecurityCritical]
internal ShutDownListener(object target)
: this(target, ShutDownEvents.All)
{
}
/// <SecurityNote>
/// Critical - accesses AppDomain.DomainUnload and AppDomain.ProcessExit events (which have link demands).
/// not SecurityTreatAsSafe.
/// </SecurityNote>
[SecurityCritical]
internal ShutDownListener(object target, ShutDownEvents events)
: base(target)
{
_flags = ((PrivateFlags)events) | PrivateFlags.Listening;
if (target == null)
{
_flags |= PrivateFlags.Static;
}
if ((_flags & PrivateFlags.DomainUnload) != 0)
{
AppDomain.CurrentDomain.DomainUnload += new EventHandler(HandleShutDown);
}
if ((_flags & PrivateFlags.ProcessExit) != 0)
{
AppDomain.CurrentDomain.ProcessExit += new EventHandler(HandleShutDown);
}
if ((_flags & PrivateFlags.DispatcherShutdown) != 0)
{
Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
dispatcher.ShutdownFinished += new EventHandler(HandleShutDown);
_dispatcherWR = new WeakReference(dispatcher);
}
}
// derived class should override this method to inform the target that a shutdown
// event has occurred. This method might be called on any thread (e.g.
// AppDomain.DomainUnload events are typically raised on worker threads).
abstract internal void OnShutDown(object target, object sender, EventArgs e);
// stop listening for shutdown events
/// <SecurityNote>
/// Critical: accesses AppDomain.DomainUnload event
/// TreatAsSafe: This code does not take any parameter or return state.
/// It simply unattaches private callbacks.
/// </SecurityNote>
[SecurityCritical,SecurityTreatAsSafe]
internal void StopListening()
{
if ((_flags & PrivateFlags.Listening) == 0)
return;
_flags = _flags & ~PrivateFlags.Listening;
if ((_flags & PrivateFlags.DomainUnload) != 0)
{
AppDomain.CurrentDomain.DomainUnload -= new EventHandler(HandleShutDown);
}
if ((_flags & PrivateFlags.ProcessExit) != 0)
{
AppDomain.CurrentDomain.ProcessExit -= new EventHandler(HandleShutDown);
}
if ((_flags & PrivateFlags.DispatcherShutdown) != 0)
{
Dispatcher dispatcher = (Dispatcher)_dispatcherWR.Target;
if (dispatcher != null)
{
dispatcher.ShutdownFinished -= new EventHandler(HandleShutDown);
}
_dispatcherWR = null;
}
}
// handle a shutdown event
private void HandleShutDown(object sender, EventArgs e)
{
// The dispatcher and AppDomain events might arrive on separate threads
// at the same time. The interlock assures that we only do the work
// once.
if (Interlocked.Exchange(ref _inShutDown, 1) == 0)
{
// ShutDown is a one-time event. Stop listening (thus releasing
// references to the ShutDownListener).
StopListening();
// do the shutdown work, unless the target has been GC'd already.
object target = Target;
if (target != null || (_flags & PrivateFlags.Static) != 0)
{
OnShutDown(target, sender, e);
}
}
}
[Flags]
enum PrivateFlags : ushort
{
DomainUnload = ShutDownEvents.DomainUnload,
ProcessExit = ShutDownEvents.ProcessExit,
DispatcherShutdown = ShutDownEvents.DispatcherShutdown,
Static = 0x4000,
Listening = 0x8000,
}
PrivateFlags _flags;
WeakReference _dispatcherWR;
int _inShutDown;
}
}
|