Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #8 from roji/scope_without_prepared_transactions

Scope without prepared transactions
  • Loading branch information...
commit 55d7f3f36f4f6f2958d88632d5e6d35d629aa8e3 2 parents 8aa8895 + cefdbaf
@franciscojunior franciscojunior authored
View
92 src/Npgsql/NpgsqlConnection.cs
@@ -122,6 +122,9 @@ public sealed class NpgsqlConnection : DbConnection, ICloneable
// Used when we closed the connector due to an error, but are pretending it's open.
private bool _fakingOpen;
+ // Used when the connection is closed but an TransactionScope is still active
+ // (the actual close is postponed until the scope ends)
+ private bool _postponingClose;
// Strong-typed ConnectionString values
private NpgsqlConnectionStringBuilder settings;
@@ -523,6 +526,11 @@ public new NpgsqlTransaction BeginTransaction(IsolationLevel level)
/// </summary>
public override void Open()
{
+ // If we're postponing a close (see doc on this variable), the connection is already
+ // open and can be silently reused
+ if (_postponingClose)
+ return;
+
CheckConnectionClosed();
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Open");
@@ -598,28 +606,50 @@ internal void EmergencyClose()
public override void Close()
{
NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Close");
-
- if (connector != null)
- {
- Promotable.Prepare();
- // clear the way for another promotable transaction
- promotable = null;
- connector.Notification -= NotificationDelegate;
- connector.Notice -= NoticeDelegate;
+ if (connector == null)
+ return;
- if (SyncNotification)
- {
- connector.RemoveNotificationThread();
- }
+ if (promotable != null && promotable.InLocalTransaction)
+ {
+ _postponingClose = true;
+ return;
+ }
- NpgsqlConnectorPool.ConnectorPoolMgr.ReleaseConnector(this, connector);
+ ReallyClose();
+ }
+ private void ReallyClose()
+ {
+ NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ReallyClose");
+ _postponingClose = false;
- connector = null;
-
- }
- }
+ // clear the way for another promotable transaction
+ promotable = null;
+
+ connector.Notification -= NotificationDelegate;
+ connector.Notice -= NoticeDelegate;
+
+ if (SyncNotification)
+ {
+ connector.RemoveNotificationThread();
+ }
+
+ NpgsqlConnectorPool.ConnectorPoolMgr.ReleaseConnector(this, connector);
+
+ connector = null;
+ }
+
+ /// <summary>
+ /// When a connection is closed within an enclosing TransactionScope and the transaction
+ /// hasn't been promoted, we defer the actual closing until the scope ends.
+ /// </summary>
+ internal void PromotableLocalTransactionEnded()
+ {
+ NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "PromotableLocalTransactionEnded");
+ if (_postponingClose)
+ ReallyClose();
+ }
/// <summary>
/// Creates and returns a <see cref="System.Data.Common.DbCommand">DbCommand</see>
@@ -653,25 +683,17 @@ public new NpgsqlCommand CreateCommand()
/// <b>false</b> when being called from the finalizer.</param>
protected override void Dispose(bool disposing)
{
- if (!disposed)
- {
- if (disposing)
- {
- NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
- Close();
- }
- else
- {
- if (FullState != ConnectionState.Closed)
- {
- NpgsqlEventLog.LogMsg(resman, "Log_ConnectionLeaking", LogLevel.Debug);
- NpgsqlConnectorPool.ConnectorPoolMgr.FixPoolCountBecauseOfConnectionDisposeFalse(this);
- }
- }
+ if (disposed)
+ return;
- base.Dispose(disposing);
- disposed = true;
+ if (disposing)
+ {
+ NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
+ Close();
}
+
+ base.Dispose(disposing);
+ disposed = true;
}
/// <summary>
@@ -900,7 +922,7 @@ private void CheckConnectionOpen()
_fakingOpen = false;
}
- if (connector == null)
+ if (_postponingClose || connector == null)
{
throw new InvalidOperationException(resman.GetString("Exception_ConnNotOpen"));
}
View
22 src/Npgsql/NpgsqlConnectorPool.cs
@@ -497,28 +497,6 @@ private static NpgsqlConnector CreateConnector(NpgsqlConnection Connection)
return new NpgsqlConnector(Connection.ConnectionStringValues.Clone(), Connection.Pooling, false);
}
-
- /// <summary>
- /// This method is only called when NpgsqlConnection.Dispose(false) is called which means a
- /// finalization. This also means, an NpgsqlConnection was leak. We clear pool count so that
- /// client doesn't end running out of connections from pool. When the connection is finalized, its underlying
- /// socket is closed.
- /// </summary>
- public void FixPoolCountBecauseOfConnectionDisposeFalse(NpgsqlConnection Connection)
- {
- ConnectorQueue Queue;
-
- // Prevent multithread access to connection pool count.
- lock (locker)
- {
- // Try to find a queue.
- if (PooledConnectors.TryGetValue(Connection.ConnectionString, out Queue) && Queue != null)
- {
- Queue.Busy.Remove(Connection.Connector);
- }
- }
- }
-
/// <summary>
/// Close the connector.
/// </summary>
View
5 src/Npgsql/NpgsqlPromotableSinglePhaseNotification.cs
@@ -36,6 +36,7 @@ internal class NpgsqlPromotableSinglePhaseNotification : IPromotableSinglePhaseN
private NpgsqlTransactionCallbacks _callbacks;
private INpgsqlResourceManager _rm;
private bool _inTransaction;
+ internal bool InLocalTransaction { get { return _npgsqlTx != null; } }
private static readonly String CLASSNAME = MethodBase.GetCurrentMethod().DeclaringType.Name;
@@ -89,6 +90,7 @@ public void Prepare()
_npgsqlTx.Cancel();
_npgsqlTx.Dispose();
_npgsqlTx = null;
+ _connection.PromotableLocalTransactionEnded();
}
}
}
@@ -114,6 +116,7 @@ public void Rollback(SinglePhaseEnlistment singlePhaseEnlistment)
_npgsqlTx.Dispose();
_npgsqlTx = null;
singlePhaseEnlistment.Aborted();
+ _connection.PromotableLocalTransactionEnded();
}
else if (_callbacks != null)
{
@@ -141,6 +144,7 @@ public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)
_npgsqlTx.Dispose();
_npgsqlTx = null;
singlePhaseEnlistment.Committed();
+ _connection.PromotableLocalTransactionEnded();
}
else if (_callbacks != null)
{
@@ -181,6 +185,7 @@ public byte[] Promote()
_npgsqlTx.Cancel();
_npgsqlTx.Dispose();
_npgsqlTx = null;
+ _connection.PromotableLocalTransactionEnded();
}
return token;
}
View
1  testsuite/noninteractive/NUnit20/BaseClassTests.cs
@@ -29,7 +29,6 @@
using NpgsqlTypes;
using NUnit.Framework;
-using NUnit.Core;
namespace NpgsqlTests
{
View
1  testsuite/noninteractive/NUnit20/CommandTests.cs
@@ -30,7 +30,6 @@
using Npgsql;
using NUnit.Framework;
-using NUnit.Core;
using System.Data;
using System.Globalization;
using System.Net;
View
1  testsuite/noninteractive/NUnit20/DataAdapterTests.cs
@@ -30,7 +30,6 @@
using NpgsqlTypes;
using NUnit.Framework;
-using NUnit.Core;
namespace NpgsqlTests
{
View
1  testsuite/noninteractive/NUnit20/DataReaderTests.cs
@@ -29,7 +29,6 @@
using NpgsqlTypes;
using NUnit.Framework;
-using NUnit.Core;
namespace NpgsqlTests
{
View
7 testsuite/noninteractive/NUnit20/NpgsqlTests2010.csproj
@@ -58,10 +58,7 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
- <Reference Include="nunit.core" />
- <Reference Include="nunit.core.interfaces" />
<Reference Include="nunit.framework" />
- <Reference Include="nunit.util" />
<Reference Include="System.configuration" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
@@ -136,10 +133,10 @@
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
- <ProjectReference Include="..\..\..\src\Npgsql2008.csproj">
+ <ProjectReference Include="..\..\..\src\Npgsql2010.csproj">
<Project>{9D13B739-62B1-4190-B386-7A9547304EB3}</Project>
<Name>Npgsql2008</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" />
-</Project>
+</Project>
View
33 testsuite/noninteractive/NUnit20/SystemTransactionsTest.cs
@@ -24,6 +24,39 @@ protected virtual string TheConnectionString
get { return _connString; }
}
+ [Test, Description("TransactionScope with a single connection, enlisting explicitly")]
+ public void SimpleTransactionScopeWithExplicitEnlist()
+ {
+ string connectionString = TheConnectionString;
+ using (var connection = new NpgsqlConnection(connectionString)) {
+ using (var scope = new TransactionScope()) {
+ connection.Open();
+ connection.EnlistTransaction(Transaction.Current);
+ var command = new NpgsqlCommand("insert into tablea(field_text) values (:p0)", connection);
+ command.Parameters.Add(new NpgsqlParameter("p0", "test"));
+ Assert.AreEqual(1, command.ExecuteNonQuery());
+ scope.Complete();
+ }
+ }
+ AssertNoTransactions();
+ }
+
+ [Test, Description("TransactionScope with a single connection, enlisting implicitly")]
+ public void SimpleTransactionScopeWithImplicitEnlist()
+ {
+ string connectionString = TheConnectionString + ";enlist=true";
+ using (var scope = new TransactionScope()) {
+ using (var connection = new NpgsqlConnection(connectionString)) {
+ connection.Open();
+ var command = new NpgsqlCommand("insert into tablea(field_text) values (:p0)", connection);
+ command.Parameters.Add(new NpgsqlParameter("p0", "test"));
+ Assert.AreEqual(1, command.ExecuteNonQuery());
+ }
+ scope.Complete();
+ }
+ AssertNoTransactions();
+ }
+
[Test]
public void DistributedTransactionRollback()
{
Please sign in to comment.
Something went wrong with that request. Please try again.