Resource Timer 

Summary

Purpose

Automatically releases inactive resources. This pattern solves the problem of resources being allocated indefinitely.

Scenario

A resource leak occurs when an application does not release (close) all the resources it acquired (opened). As the offending application continue to consume more resources without releasing them, the supply available for other applications simply depletes. Resource leaks are commons software defects and are often difficult to debug. To make matter worse, it is often very difficult to recreate resource leaks in debugging environments. The best solution is to avoid leaks in the first place by employing disciplined resource-management strategies. Isolating code that allocates resources within a single module will make it easier to handle resource leaks.

Garbage collection offers a possibility  While it is quite effective for fine-grained objects like buffers and internal data structures, it is not usually effective for database resources. This is because most database drivers and resource managers retain references to each resource they create. These references prevent garbage collectors from identifying them (the references) as being available for garbage collection.

The Resource Timer pattern offers a better solution for managing database resources by using a timer to keep track of a resource's activity. A resource is inactive when a client has not invokes any of its operations. A resource timer expires when it reaches a predefined (and configurable) inactivity threshold. You can build resource timers to work with a variety of database resources, however, it is up to each resource type to precisely define what activity it entails and what cleaning operations should occur when the associated resource-timer expires. Some example of where you can apply a resource timer:

Structure & UML

The following diagram illustrates the static structure of the Resource Timer:

The Resource interface represents any database resource such as a connection (IDbConnection). ConcreteResource (SqlConnection) represents a database platform implementation (SQL Server) of that interface. Client code is normally written in terms of the Resource (IDbConnection) interface but interact directly with ConcreteResource (SqlConnection). Obviously this allows your code to work with any compliant implementation of the Resource (IDbConnection) interface.

TimedResource is another Resource implementation that is a specialized Resource Decorator. TimedResource attaches a ResourceTimer to its Resource implementation. It is TimedResource's responsibility to designate what activity it entails. It does this by setting its ResourceTimer for each operation that corresponds to a resource activity. In many cases it does this for any operation. Once a client decorates a ConcreteResource object with a TimedResource, it interacts with it like any other Resource implementation - the additional TimedResource functionality is mostly transparent to clients.

The figure below illustrates a TimedResource initialization. a TimedResource starts an associated ResourceTimer when the client first opens the TimedResource.

The figure below illustrates a TimedResource utilization. A TimedResource delegates all its operations to its reference ConcreteResource object. If the operation indicates a resource activity, it also resets the associated ResourceTimer. It is best to reset the timer before delegating the operation to prevent it from expiring during long-running operations. The figure below also applies to the case when a client closes a TimedResource. In this case, OpeationA becomes Close and Reset becomes Stop:

The figure below illustrates a ResourceTimer expiration. A ResourceTimer notifies its TimedResource usually via an event. The TimedResource handles this event by closing its referenced ConcreteResource object:

Example

public class BaseConnectionDecorator : System.Data.IDbConnection
{
    // Data members
    private IDbConnection m_connection = null;

    // Constructors
    public BaseConnectionDecorator( IDbConnection conn)
    {
        m_connection = conn;
    }

    // IDbConnection implementation
    // Methods
    virtual public IDbTransaction    BeginTransaction(IsolationLevel level) { return m_connection.BeginTransaction( level ); }
    virtual public IDbTransaction    BeginTransaction()                     { return m_connection.BeginTransaction(); }
    virtual public void              Close()                                { m_connection.Close(); }
    virtual public void              ChangeDatabase( string strDBName )     { m_connection.ChangeDatabase( strDBName ); }
    virtual public IDbCommand        CreateCommand()                        { return m_connection.CreateCommand(); }
    virtual public void              Open()                                 { m_connection.Open(); }
    public void                      Dispose()                              { m_connection.Dispose(); }
    // Properties
    public string ConnectionString 
    {
        get { return m_connection.ConnectionString; }
        set { m_connection.ConnectionString = (string)value; }
    }
    public int ConnectionTimeout
    {
        get { return m_connection.ConnectionTimeout; }
    }
    public string Database
    {
        get { return m_connection.Database; }
    }
    public ConnectionState State
    {
        get { return m_connection.State; }
    }
}

public class TimedConnection : BaseConnectionDecorator
{
    // Data members
    private bool m_bClosed = true;
    private ConnectionTimer m_Timer = null;

    // Constructors
    public TimedConnection( IDbConnection conn) : base(conn)
    {
        // Initialize resource timer with a pre-defined timeout period
        m_Timer = new ConnectionTimer( this );
    }

    // IDbConnection overriding implementation to account for timer
    public override void Open()
    { 
        // Open connection if not already open
        if (m_bClosed)
        {
            m_bClosed = false;
            m_Timer.Start();
            base.Open ();
        }
    }

    // The overridden close method closes the connection and stops the timer if the connection
    // was not already closed by an elapsed timer
    public override void Close()
    {
        // Close connection if not already closed by the timer. The lock synchronizes access
        // as Close could be called by both Client and Timer around the same time
        lock( this )
        {
            if (!m_bClosed)
            {
                m_bClosed = true;
                m_Timer.Stop();
                base.Close ();
            }
        }
    }

    // All other IDbConnection overridden implementations just need to indicate activity by
    // resetting the timer
    public override IDbTransaction BeginTransaction(IsolationLevel level)
    {
        m_Timer.Reset();
        return base.BeginTransaction( level ); 
    }
    public override IDbTransaction BeginTransaction()
    {
        m_Timer.Reset();
        return base.BeginTransaction(); 
    }
    public override void ChangeDatabase( string strDBName )
    { 
        m_Timer.Reset();
        base.ChangeDatabase( strDBName ); 
    }
    public override IDbCommand CreateCommand()
    {
        m_Timer.Reset();
        return base.CreateCommand();
    }

    // This event handler is called by the timer when the timer expires. TimedConnection
    // must close the connection on receipt of this timer
    // Timer event handler
    public void TimedConnection_Elapsed( object sender, System.Timers.ElapsedEventArgs e)
    {
        // The timer has fired. Therefore, close the connection
        Close();
    }
}

public class ConnectionTimer
{
    // Data members
    const long TIMEOUT = 10000;                         // milliseconds (only for testing purposes)
    private bool m_bStarted = false;
    private TimedConnection m_Connection = null;
    private System.Timers.Timer m_ConnectionTimer = new System.Timers.Timer( TIMEOUT );

    // Constructors
    public ConnectionTimer(TimedConnection conn)
    {
        // Setup timer event handler to be a method in the TimedConnection instance that 
        // created this ConnectionTimer instance
        m_ConnectionTimer.Elapsed += new ElapsedEventHandler(conn.TimedConnection_Elapsed);
    }

    // Public interface
    public void Start()
    {
        if (!m_bStarted)
        {
            m_bStarted = true;
            m_ConnectionTimer.Start();
        }
    }

    public void Stop()
    {
        if (m_bStarted)
        {
            m_bStarted = false;
            m_ConnectionTimer.Stop();
        }
    }

    public void Reset()
    {
        m_ConnectionTimer.Stop();
        m_ConnectionTimer.Start();
    }
}

// Main client
ResourceTimer.TimedConnection conn = new ResourceTimer.TimedConnection( new SqlConnection() );
conn.ConnectionString = "Initial Catalog=Northwind;Data Source=localhost;Integrated Security=SSPI;";
conn.Open();
System.Threading.Thread.Sleep( 10000 );        // Simulate a very long operation
conn.Close();        

 

 

Applicability

Use this pattern when:

Strategies / Variants

Consider these strategies when implementing the Resource Timer pattern: 

Benefits

Liabilities

Related Patterns