* Added initial support for connection pool.
* Fixed Connection testsuite for nested transactions with connection pool.
* Removed some finalizers which were causing segmentation fault.
svn path=/trunk/mcs/; revision=22203
using System.Net;
using System.Net.Sockets;
using System.Resources;
+using System.Collections;
using Mono.Security.Protocol.Tls;
namespace Npgsql
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Open");
- // Create a new TLS Session
try
{
TcpClient tcpc = new TcpClient(context.ServerName, Int32.Parse(context.ServerPort));
stream = new SslClientStream(tcpc.GetStream(), context.ServerName, true, Mono.Security.Protocol.Tls.SecurityProtocolType.Default);
}
}
- context.Stream = stream;
+ context.Connector.Stream = stream;
+
+
}
catch (TlsException e)
{
throw new NpgsqlException(e.ToString());
}
+
NpgsqlEventLog.LogMsg(resman, "Log_ConnectedTo", LogLevel.Normal, context.ServerName, context.ServerPort);
ChangeState(context, NpgsqlConnectedState.Instance);
context.Startup();
+
}
}
/// <summary>
/// Finalizer for <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
/// </summary>
- ~NpgsqlCommand ()
+ /*~NpgsqlCommand ()
{
Dispose(false);
- }
+ }*/
// Public properties.
/// <summary>
CheckNotification();
-
+
// Get the resultsets and create a Datareader with them.
return new NpgsqlDataReader(connection.Mediator.GetResultSets(), connection.Mediator.GetCompletedResponses(), connection, cb);
}
/// </summary>
protected override void Dispose (bool disposing)
{
- NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
+
if (disposing)
{
+ // Only if explicitly calling Close or dispose we still have access to
+ // managed resources.
+ NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
if (connection != null)
{
connection.Dispose();
}
}
- ~NpgsqlCommandBuilder ()
+ /*~NpgsqlCommandBuilder ()
{
Dispose(false);
- }
+ }*/
}
/// PostgreSQL server.
/// </summary>
[System.Drawing.ToolboxBitmapAttribute(typeof(NpgsqlConnection))]
- public sealed class NpgsqlConnection : Component, IDbConnection, IDisposable
+ public sealed class NpgsqlConnection : Component, IDbConnection
{
//Changed the Name of this event because events usually don't start with 'On' in the .Net-Framework
// (but their handlers do ;-)
// These are for ODBC connection string compatibility
internal readonly String ODBC_USERID = "UID";
internal readonly String ODBC_PASSWORD = "PWD";
+
+ // These are for the connection pool
+ internal readonly String MIN_POOL_SIZE = "MINPOOLSIZE";
+ internal readonly String MAX_POOL_SIZE = "MAXPOOLSIZE";
// Values for possible CancelRequest messages.
private NpgsqlBackEndKeyData backend_keydata;
private Stream stream;
+ private Connector _connector;
+
private Encoding connection_encoding;
private Boolean _supportsPrepare = false;
ParseConnectionString();
}
- /// <summary>
- /// Finalizer for NpgsqlConnection
- /// </summary>
- ~NpgsqlConnection ()
- {
- Dispose(false);
- }
-
+
/// <summary>
/// Gets or sets the string used to open a SQL Server database.
/// </summary>
connection_string_values[CONN_PORT] = PG_PORT;
if (connection_string_values[SSL_ENABLED] == null)
connection_string_values[SSL_ENABLED] = "no";
-
+
+ if (connection_string_values[MIN_POOL_SIZE] == null)
+ connection_string_values[MIN_POOL_SIZE] = "1";
+ if (connection_string_values[MAX_POOL_SIZE] == null)
+ connection_string_values[MAX_POOL_SIZE] = "-1";
+
try
{
-
+
// Check if the connection is already open.
if (connection_state == ConnectionState.Open)
throw new NpgsqlException(resman.GetString("Exception_ConnOpen"));
-
- // Try first connect using the 3.0 protocol...
- CurrentState.Open(this);
-
-
- // Check if there were any errors.
- if (_mediator.Errors.Count > 0)
+ lock(ConnectorPool.ConnectorPoolMgr)
{
- // Check if there is an error of protocol not supported...
- // As the message can be localized, just check the initial unlocalized part of the
- // message. If it is an error other than protocol error, when connecting using
- // version 2.0 we shall catch the error again.
- if (((String)_mediator.Errors[0]).StartsWith("FATAL"))
- {
- // Try using the 2.0 protocol.
- _mediator.Reset();
- CurrentState = NpgsqlClosedState.Instance;
- BackendProtocolVersion = ProtocolVersion.Version2;
- CurrentState.Open(this);
- }
-
- // Keep checking for errors...
- if(_mediator.Errors.Count > 0)
+ Connector = ConnectorPool.ConnectorPoolMgr.RequestConnector(ConnectionString, false);
+ Connector.InUse = true;
+ }
+
+ if (!Connector.IsInitialized)
+ {
+
+ // Reset state to initialize new connector in pool.
+ CurrentState = NpgsqlClosedState.Instance;
+
+ // Try first connect using the 3.0 protocol...
+ CurrentState.Open(this);
+
+ // Change the state of connection to open.
+ connection_state = ConnectionState.Open;
+
+ // Check if there were any errors.
+ if (_mediator.Errors.Count > 0)
{
- StringWriter sw = new StringWriter();
- sw.WriteLine(resman.GetString("Exception_OpenError"));
- uint i = 1;
- foreach(string error in _mediator.Errors)
+ // Check if there is an error of protocol not supported...
+ // As the message can be localized, just check the initial unlocalized part of the
+ // message. If it is an error other than protocol error, when connecting using
+ // version 2.0 we shall catch the error again.
+ if (((String)_mediator.Errors[0]).StartsWith("FATAL"))
{
- sw.WriteLine("{0}. {1}", i++, error);
+ // Try using the 2.0 protocol.
+ _mediator.Reset();
+ CurrentState = NpgsqlClosedState.Instance;
+ BackendProtocolVersion = ProtocolVersion.Version2;
+ CurrentState.Open(this);
+ }
+
+ // Keep checking for errors...
+ if(_mediator.Errors.Count > 0)
+ {
+ StringWriter sw = new StringWriter();
+ sw.WriteLine(resman.GetString("Exception_OpenError"));
+ uint i = 1;
+ foreach(string error in _mediator.Errors)
+ {
+ sw.WriteLine("{0}. {1}", i++, error);
+ }
+ CurrentState = NpgsqlClosedState.Instance;
+ _mediator.Reset();
+ throw new NpgsqlException(sw.ToString());
}
- CurrentState = NpgsqlClosedState.Instance;
- _mediator.Reset();
- throw new NpgsqlException(sw.ToString());
}
+
+ backend_keydata = _mediator.GetBackEndKeyData();
+
+
+
+ // Get version information to enable/disable server version features.
+ // Only for protocol 2.0.
+ if (BackendProtocolVersion == ProtocolVersion.Version2)
+ {
+ NpgsqlCommand command = new NpgsqlCommand("select version();set DATESTYLE TO ISO;", this);
+ _serverVersion = (String) command.ExecuteScalar();
+ }
+
+ Connector.ServerVersion = _serverVersion;
+
}
-
- backend_keydata = _mediator.GetBackEndKeyData();
-
- // Change the state of connection to open.
- connection_state = ConnectionState.Open;
-
- // Get version information to enable/disable server version features.
- // Only for protocol 2.0.
- if (BackendProtocolVersion == ProtocolVersion.Version2)
- {
- NpgsqlCommand command = new NpgsqlCommand("select version();set DATESTYLE TO ISO;", this);
- _serverVersion = (String) command.ExecuteScalar();
- }
-
-
//NpgsqlCommand commandEncoding = new NpgsqlCommand("show client_encoding", this);
//String serverEncoding = (String)commandEncoding.ExecuteScalar();
//if (serverEncoding.Equals("UNICODE"))
// connection_encoding = Encoding.UTF8;
-
-
-
+
+
+ // Connector was obtained from pool.
+ // Do a mini initialization in the state machine.
+
+ connection_state = ConnectionState.Open;
+ _serverVersion = Connector.ServerVersion;
+ CurrentState = NpgsqlReadyState.Instance;
+
ProcessServerVersion();
_oidToNameMapping = NpgsqlTypesHelper.LoadTypesMapping(this);
-
-
}
/// </summary>
public void Close()
{
- NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Close");
-
- try
- {
- if ((connection_state == ConnectionState.Open))
- {
- CurrentState.Close(this);
- }
- }
- catch (IOException e)
- {
- throw new NpgsqlException(resman.GetString("Exception_CloseError"), e);
- }
- finally
- {
- // Even if an exception occurs, let object in a consistent state.
- if (stream != null)
- stream.Close();
- connection_state = ConnectionState.Closed;
- }
+ Dispose(true);
+
}
/// <summary>
/// <b>false</b> to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
- NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose", disposing);
- if (disposing == true)
+ if (disposing)
{
- if (this.connection_state == ConnectionState.Open)
- this.Close();
- this.connection_string = null;
+ // Only if explicitly calling Close or dispose we still have access to
+ // managed resources.
+ NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose", disposing);
+
+ try
+ {
+ if ((connection_state == ConnectionState.Open))
+ {
+ CurrentState.Close(this);
+ }
+
+ }
+ catch (IOException e)
+ {
+ throw new NpgsqlException(resman.GetString("Exception_CloseError"), e);
+ }
+ finally
+ {
+ // Even if an exception occurs, let object in a consistent state.
+ /*if (stream != null)
+ stream.Close();*/
+ connection_state = ConnectionState.Closed;
+ }
+
}
base.Dispose (disposing);
}
/// Database - Database name. Defaults to user name if not specified
/// User - User name
/// Password - Password for clear text authentication
+ /// MinPoolSize - Min size of connection pool
+ /// MaxPoolSize - Max size of connection pool
/// </summary>
private void ParseConnectionString()
{
private void ProcessServerVersion ()
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ProcessServerVersion");
+ // FIXME: Do a better parsing of server version to avoid
+ // hardcode version numbers.
if (BackendProtocolVersion == ProtocolVersion.Version2)
{
+ // With protocol version 2, only 7.3 version supports prepare.
SupportsPrepare = (_serverVersion.IndexOf("PostgreSQL 7.3") != -1);
}
else
{
// 3.0+ version is set by ParameterStatus message.
- SupportsPrepare = (_serverVersion.IndexOf("7.4") != -1);
+ // On protocol 3.0, 7.4 and above support it.
+ SupportsPrepare = (_serverVersion.IndexOf("7.4") != -1)
+ || (_serverVersion.IndexOf("7.5") != -1);
}
}
internal Stream Stream {
get
{
- return stream;
+ return _connector.Stream;
}
set
{
stream = value;
}
}
+
+ internal Connector Connector
+ {
+ get
+ {
+ return _connector;
+ }
+ set
+ {
+ _connector = value;
+ }
+ }
/*
public bool useSSL()
_backendProtocolVersion = value;
}
}
+
+ internal Int32 MinPoolSize {
+ get
+ {
+ return Int32.Parse((String)connection_string_values[MIN_POOL_SIZE]);
+ }
+ }
+
+ internal Int32 MaxPoolSize {
+ get
+ {
+ return Int32.Parse((String)connection_string_values[MAX_POOL_SIZE]);
+ }
+ }
}
using System;
using System.Net.Sockets;
+using System.IO;
namespace Npgsql
{
/// <summary>
/// !!! Helper class, for compilation only.
/// </summary>
- internal class Socket
- {
- internal void Open()
- {
- return;
- }
- internal void Close()
- {
- return;
- }
- }
-
+
/// <summary>
/// Connector implements the logic for the Connection Objects to
/// access the physical connection to the database, and isolate
internal class Connector
{
/// <value>Buffer for the public Pooled property</value>
- private bool mPooled;
+ private Boolean _inUse;
+
+
+ private Stream _stream;
+
+ // This is information about the connection
+ // this connector is holding. For while only the server version is used.
+ // Change later for a more generic way to keep it. (Hashtable)
+ private String _serverVersion;
+
+ private Boolean _isInitialized;
+
+ private Boolean mPooled;
+ private Boolean mOpen;
/// <value>Chain references for implementing a double linked
/// list</value>
this.mPooled = value;
}
}
+
+ internal String ServerVersion
+ {
+ get
+ {
+ return _serverVersion;
+ }
+
+ set
+ {
+ _serverVersion = value;
+ }
+ }
+
+
+ internal Stream Stream {
+ get
+ {
+ return _stream;
+ }
+ set
+ {
+ _stream = value;
+ _isInitialized = true;
+ }
+ }
+
+ internal Boolean IsInitialized
+ {
+ get
+ {
+ return _isInitialized;
+ }
+
+ }
+
/// <value>Buffer for the public Shared property</value>
private bool mShared;
/// <value>Provides physical access to the server</value>
// !!! to be fixed
- private Npgsql.Socket Socket;
-
- /// <value>True if the physical connection is open.</value>
- private bool mOpen;
+ //private Npgsql.Socket Socket;
+
/// <summary>
/// Default constructor. Creates a pooled Connector by default.
/// </summary>
public Connector()
{
- this.Pooled = true;
+ Pooled = true;
+ _isInitialized = false;
}
/// <summary>
/// </summary>
internal Connector( string ConnectString, bool Shared )
{
- this.ConnectString = ConnectString;
- this.Shared = Shared;
- this.Pooled = true;
+ ConnectString = ConnectString;
+ Shared = Shared;
+ Pooled = true;
}
/// <summary>
/// Method of the connection pool manager.</remarks>
internal void Open()
{
- this.Socket = new Npgsql.Socket();
- this.Socket.Open(); // !!! to be fixed
- this.mOpen = true;
+ //this.Socket = new Npgsql.Socket();
+ //this.Socket.Open(); // !!! to be fixed
+ //this.mOpen = true;
+ }
+
+
+ internal Boolean InUse {
+ get
+ {
+ return _inUse;
+ }
+ set
+ {
+ _inUse = value;
+ }
+
}
/// <summary>
/// evaluation inside this method, so they are left in their current state.
/// They get new meaning again when the connector is requested from the
/// pool manager later. </remarks>
- public void Release()
+ /*public void Release()
{
if ( this.mShared )
{
// Instead they are (implicitly) handed over to the
// garbage collection.
// !!! to be fixed
- this.Socket.Close();
+ //this.Socket.Close();
}
}
- }
+ }*/
}
}
// 0.00.0000 - 06/17/2002 - ulrich sprick - creation
using System;
+using System.Collections;
using Npgsql;
namespace Npgsql
/// <value>Unique static instance of the connector pool
/// mamager.</value>
internal static ConnectorPool ConnectorPoolMgr = new Npgsql.ConnectorPool();
+
+ public ConnectorPool()
+ {
+ PooledConnectors = new Hashtable();
+ }
+
- /// <value>List of unused, pooled connectors avaliable to the
+ /// <value>Map of index to unused pooled connectors, avaliable to the
/// next RequestConnector() call.</value>
- /// <remarks>Points to the head of a double linked list</remarks>
- internal Npgsql.Connector PooledConnectors;
+ /// <remarks>This hasmap will be indexed by connection string.
+ /// This key will hold a list of the pooled connectors available to be used.</remarks>
+ internal Hashtable PooledConnectors;
/// <value>List of used, shared conncetors.</value>
/// <remarks>Points to the head of a double linked list</remarks>
/// Inserts a connector at the head of a pooled connector list.
/// </summary>
/// <param name="Connector">The connctor to be inserted</param>
- internal void InsertPooledConnector( Npgsql.Connector Connector )
+ /*internal void InsertPooledConnector( Npgsql.Connector Connector )
{
if ( this.PooledConnectors == null ) // the list is empty
{
}
// point the list to the new head
this.PooledConnectors = Connector;
+ }*/
+
+
+ internal Int32 GetPoolSize(String connectionString)
+ {
+ ArrayList pool = (ArrayList)PooledConnectors[connectionString];
+ if (pool == null)
+ return 0;
+ else
+ return pool.Count;
+
+
}
/// <summary>
/// <param name="Shared">Allows multiple connections
/// on a single connector. </param>
/// <returns>A pooled connector object.</returns>
- internal Npgsql.Connector RequestConnector ( string ConnectString,
+ internal Npgsql.Connector RequestConnector ( String connectionString,
bool Shared )
{
- Npgsql.Connector Connector;
+ Connector connector;
+ ArrayList connectorPool = null;
if ( Shared )
{
// if a shared connector is requested then the
// Shared Connector List is searched first
- for ( Connector = Npgsql.ConnectorPool.ConnectorPoolMgr.SharedConnectors;
+ /*for ( Connector = Npgsql.ConnectorPool.ConnectorPoolMgr.SharedConnectors;
Connector != null; Connector = Connector.Next )
{
- if ( Connector.ConnectString == ConnectString )
+ if ( Connector.ConnectString == connectionString )
{ // Bingo!
// Return the shared connector to caller
Connector.mShareCount++;
return Connector;
}
- }
+ }*/
+
+ return null;
}
else
{
// if a shared connector could not be found or a
// nonshared connector is requested, then the pooled
// (unused) connectors are beeing searched.
-
- for ( Connector = Npgsql.ConnectorPool.ConnectorPoolMgr.PooledConnectors;
- Connector != null; Connector = Connector.Next )
+
+
+ connectorPool = (ArrayList)PooledConnectors[connectionString];
+
+ if (connectorPool == null)
{
- if ( Connector.ConnectString == ConnectString )
- { // Bingo!
- // Remove the Connector from the pooled connectors list.
- this.CutOutConnector( Connector );
- }
- Connector.Shared = Shared;
- if ( Shared )
- {
- // Shared Connectors are then put in the shared
- // connectors list in order to be used by
- // additional clients.
- this.InsertSharedConnector( Connector );
- Connector.mShareCount++;
- }
- // done...
- return Connector;
+ connectorPool = new ArrayList();
+ PooledConnectors[connectionString] = connectorPool;
}
- }
+
+
+ // Now look for an available connector.
+
+
+ foreach (Connector c in connectorPool)
+ {
+ if (!c.InUse)
+ return c;
+ }
+
+ // No suitable connector could be found, so create new one
+ connector = new Npgsql.Connector( connectionString, Shared );
- // No suitable connector could be found, so create new one
- Connector = new Npgsql.Connector( ConnectString, Shared );
+ connectorPool.Add(connector);
+
- // Shared connections are added to the shared connectors list
- if ( Shared )
- {
- this.InsertSharedConnector( Connector );
- Connector.mShareCount++;
+ // and then returned to the caller
+ return connector;
+
+
+
}
- // and then returned to the caller
- return Connector;
+
}
}
}
CheckCanRead();
- if (i < 0 || _rowIndex < 0)
- throw new InvalidOperationException("Cannot read data.");
+ if (i < 0)
+ throw new InvalidOperationException("Cannot read data. Column less than 0 specified.");
+ if (_rowIndex < 0)
+ throw new InvalidOperationException("Cannot read data. DataReader not initialized. Maybe you forgot to call Read()?");
return ((NpgsqlAsciiRow)_currentResultset[_rowIndex])[i];
{
Console.WriteLine(message);
}
-
+
if (logfile != null)
{
if (logfile != "")
protected ResourceManager resman = null;
public virtual void Open(NpgsqlConnection context)
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public virtual void Startup(NpgsqlConnection context)
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public virtual void Authenticate(NpgsqlConnection context, string password)
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public virtual void Query(NpgsqlConnection context, NpgsqlCommand command)
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public virtual void Ready( NpgsqlConnection context )
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public virtual void FunctionCall(NpgsqlConnection context, NpgsqlCommand command)
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public virtual void Parse(NpgsqlConnection context, NpgsqlParse parse)
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public virtual void Flush(NpgsqlConnection context)
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public virtual void Sync(NpgsqlConnection context)
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public virtual void Bind(NpgsqlConnection context, NpgsqlBind bind)
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public virtual void Execute(NpgsqlConnection context, NpgsqlExecute execute)
- {}
+ {
+ throw new InvalidOperationException("Internal Error! " + this);
+ }
public NpgsqlState()
{
public virtual void Close( NpgsqlConnection context )
{
- NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Close");
+ /*NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Close");
if ( context.State == ConnectionState.Open )
{
Stream stream = context.Stream;
PGUtil.WriteInt32(stream, 4);
stream.Flush();
}
- }
- ChangeState( context, NpgsqlClosedState.Instance );
+ }*/
+
+ context.Connector.InUse = false;
+ context.Connector = null;
+ //ChangeState( context, NpgsqlClosedState.Instance );
}
///<summary> This method is used by the states to change the state of the context.
// Get the date time parsed in all expected formats for timestamp.
return DateTime.ParseExact(data,
- new String[] {"yyyy-MM-dd HH:mm:ss.fff", "yyyy-MM-dd HH:mm:ss.ff", "yyyy-MM-dd HH:mm:ss.f", "yyyy-MM-dd HH:mm:ss"},
+ new String[] {"yyyy-MM-dd HH:mm:ss.ffffff", "yyyy-MM-dd HH:mm:ss.fffff", "yyyy-MM-dd HH:mm:ss.ffff", "yyyy-MM-dd HH:mm:ss.fff", "yyyy-MM-dd HH:mm:ss.ff", "yyyy-MM-dd HH:mm:ss.f", "yyyy-MM-dd HH:mm:ss"},
DateTimeFormatInfo.InvariantInfo,
DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AllowWhiteSpaces);
private static String ConvertByteArrayToBytea(Byte[] byteArray)
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertByteArrayToBytea");
- Int32 len = byteArray.Length;
- Char[] res = new Char [len * 5];
- for (Int32 i = 0; i <len; i++)
+ int len = byteArray.Length;
+ char[] res = new char[len * 5];
+ for (int i=0, o=0; i<len; ++i, o += 5)
{
- res [(i*5)] = '\\';
- res [(i*5)+1] = '\\';
- res [(i*5)+2] = (Char) (((byteArray[i] & 0xC0) >> 6) + '0');
- res [(i*5)+3] = (Char) (((byteArray[i] & 0x38) >> 3) + '0');
- res [(i*5)+4] = (Char) ((byteArray[i] & 0x07) + '0');
+ byte item = byteArray[i];
+ res[o] = res[o + 1] = '\\';
+ res[o + 2] = (char)('0' + (7 & (item >> 6)));
+ res[o + 3] = (char)('0' + (7 & (item >> 3)));
+ res[o + 4] = (char)('0' + (7 & item));
}
-
- return new String (res);
+ return new String(res);
}
}
{
_conn.Open();
- NpgsqlTransaction t = _conn.BeginTransaction();
+ NpgsqlTransaction t = null;
+ try
+ {
+ t = _conn.BeginTransaction();
- t = _conn.BeginTransaction();
+ t = _conn.BeginTransaction();
+ }
+ catch(Exception e)
+ {
+ // Catch exception so we call rollback the transaction initiated.
+ // This way, the connection pool doesn't get a connection with a transaction
+ // started.
+ t.Rollback();
+ throw e;
+ }
}