Enlisting an Oracle database connection throws an AccessViolationException  
Author Message
msp1502





PostPosted: .NET Framework Data Access and Storage, Enlisting an Oracle database connection throws an AccessViolationException Top

Hi,

I use .NET 2.0, Oracle Express Edition 10g Release 2 (10.2.0.1), and System.Data.OracleClient.

I wrote a resource manager that implements System.Transactions.IEnlistmentNotification.
The resource manager itself is enlisted as a volatile resource in Transaction.Current using

Transaction.Current.EnlistVolatile(resourceManager, EnlistmentOptions.EnlistDuringPrepareRequired);

Within IEnlistmentNotification.Prepare I want to enlist a new database connection.
If I resolve the data source in my connection string I get an AccessViolationException:

"An unhandled exception of type 'System.AccessViolationException' occurred in System.Data.OracleClient.dll
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

Here is the code of my IEnlistmentNotification.Prepare method:

void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
{
// works fine:

// throws System.AccessViolationException:

"(ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521)))" +
"(CONNECT_DATA = (SERVICE_NAME = xe)));" +
"Enlist=false;Pooling=false;Persist Security Info=true;Unicode=true";

System.Data.OracleClient.OracleConnection connection = new System.Data.OracleClient.OracleConnection(connstr);

// open still works...
connection.Open();

// ...EnlistTransaction throws a System.AccessViolationException
connection.EnlistTransaction(Transaction.Current);

// ...
// some code
// ...

connection.Close();
preparingEnlistment.Prepared();
}

What's the problem with my second connection string

Regards,
Michael



.NET Development7  
 
 
Sarah Parra - MSFT





PostPosted: .NET Framework Data Access and Storage, Enlisting an Oracle database connection throws an AccessViolationException Top

Hi Michael,

I don't see anything wrong with your connection string. In fact, I created my own extremely simple resource manager class and was able to open the connection in Prepare like you are doing, without error. We are using the same version of the Oracle client DLLs and System.Data.OracleClient. Can you verify if my code works for you The following is my code:

using System;
using System.Data.OracleClient;
using System.Transactions;

namespace OracleConsoleApp
{
class Program
{
static void Main(string[] args)
{
using (TransactionScope scope = new TransactionScope())
{
myEnlistmentClass resourceManager = new myEnlistmentClass();
Transaction.Current.EnlistVolatile(resourceManager, EnlistmentOptions.EnlistDuringPrepareRequired);
scope.Complete();
}
}
}

class myEnlistmentClass : IEnlistmentNotification
{
public void Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("Prepare notification received");

// throws System.AccessViolationException:

"(ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = myServer)(PORT = 1521)))" +
"(CONNECT_DATA = (SERVICE_NAME = myService)));" +
"Enlist=false;Pooling=false;Persist Security Info=true;Unicode=true";

System.Data.OracleClient.OracleConnection connection = new System.Data.OracleClient.OracleConnection(connstr);

connection.Open();
Console.WriteLine("Connected successfully");

connection.EnlistTransaction(Transaction.Current);

connection.Close();
preparingEnlistment.Prepared();
}

public void Commit(Enlistment enlistment)
{
Console.WriteLine("Commit notification received");
enlistment.Done();
}

public void Rollback(Enlistment enlistment)
{
Console.WriteLine("Rollback notification received");
enlistment.Done();
}

public void InDoubt(Enlistment enlistment)
{
Console.WriteLine("In doubt notification received");
enlistment.Done();
}
}
}

If this doesn't fail for you, is there anything you are doing that is significantly different If you would like to send me a sample that reproduces the problem, feel free to e-mail it to the address in my forum profile. To do that, REMOVE the "online" from the address.

Thanks,
Sarah




 
 
msp1502





PostPosted: .NET Framework Data Access and Storage, Enlisting an Oracle database connection throws an AccessViolationException Top

Hi Sarah,

thank you for your sample app.

You are using a TransactionScope instead of a CommittableTransaction. When the TransactionScope instance is disposed, Transaction.Current will be reset to the original value (in our case to null). It happens BEFORE IEnlistmentNotification.Prepared is raised. That leads to a null value of Transaction.Current within IEnlistmentNotification.Prepared. Could this be a bug in class TransactionScope

That's why no exception will be thrown in your code because Transaction.Current is null within IEnlistmentNotification.Prepare, and nothing will be enlisted because there is no active current transaction.

If you are using a CommittableTransaction and set Transaction.Current explicitly to this CommittableTransaction instance, Transaction.Current is still valid (and active) in IEnlistmentNotification.Prepare. You can use this value to enlist the OracleConnection.

Using the following connection string we are able to enlist our OracleConnection in Transaction.Current:
"Data Source=xe;Persist Security Info=True;User ID=hr;Password=hr;Unicode=True;Enlist=false;Pooling=false;"

But using the next connection string the system throws an AccessViolationException:
"User Id=hr;Password=hr;Data Source=(DESCRIPTION = " +
"(ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521)))" +
"(CONNECT_DATA = (SERVICE_NAME = xe)));" +
"Enlist=false;Pooling=false;Persist Security Info=true;Unicode=true"

The only difference is the Data Source parameter.

Here is a modification of your console app that will throw the System.AccessViolationException:

using System;
using System.Data.OracleClient;
using System.Diagnostics;
using System.Transactions;

namespace OracleEnlistmentTest
{
class Program
{
static void Main(string[] args)
{
using (CommittableTransaction tr = new CommittableTransaction(TimeSpan.FromHours(1)))
{
Transaction.Current = tr;
myEnlistmentClass resourceManager = new myEnlistmentClass();
Transaction.Current.EnlistVolatile(resourceManager, EnlistmentOptions.EnlistDuringPrepareRequired);
tr.Commit();
}
}
}

class myEnlistmentClass: IEnlistmentNotification
{
public void Prepare(PreparingEnlistment preparingEnlistment)
{
Console.WriteLine("Prepare notification received");


"(ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = myServer)(PORT = 1521)))" +
"(CONNECT_DATA = (SERVICE_NAME = myService)));" +
"Enlist=false;Pooling=false;Persist Security Info=true;Unicode=true";

OracleConnection connection = new OracleConnection(connstr);

connection.Open();
Console.WriteLine("Connected successfully");

Debug.Assert(Transaction.Current != null);
Debug.Assert(Transaction.Current.TransactionInformation.Status == TransactionStatus.Active);

// throws System.AccessViolationException
connection.EnlistTransaction(Transaction.Current);

connection.Close();
preparingEnlistment.Prepared();
}

public void Commit(Enlistment enlistment)
{
Console.WriteLine("Commit notification received");
enlistment.Done();
}

public void Rollback(Enlistment enlistment)
{
Console.WriteLine("Rollback notification received");
enlistment.Done();
}

public void InDoubt(Enlistment enlistment)
{
Console.WriteLine("In doubt notification received");
enlistment.Done();
}
}
}


Regards,
Michael


 
 
Florin Lazar - MSFT





PostPosted: .NET Framework Data Access and Storage, Enlisting an Oracle database connection throws an AccessViolationException Top

Hi Michael,

Is it possible to post the stack of the AccessViolation exception

Thanks.



 
 
msp1502





PostPosted: .NET Framework Data Access and Storage, Enlisting an Oracle database connection throws an AccessViolationException Top

Hi,

here is the stack trace:

at System.Data.Common.UnsafeNativeMethods.OraMTSEnlCtxGet(Byte[] lpUname, Byte[] lpPsswd, Byte[] lpDbnam, OciHandle pOCISvc, OciHandle pOCIErr, UInt32 dwFlags, IntPtr& pCtxt)
at System.Data.OracleClient.TracedNativeMethods.OraMTSEnlCtxGet(Byte[] userName, Byte[] password, Byte[] serverName, OciHandle pOCISvc, OciHandle pOCIErr, IntPtr& pCtxt)
at System.Data.OracleClient.OciEnlistContext..ctor(Byte[] userName, Byte[] password, Byte[] serverName, OciServiceContextHandle serviceContextHandle, OciErrorHandle errorHandle)
at System.Data.OracleClient.OracleInternalConnection.Enlist(String userName, String password, String serverName, Transaction transaction, Boolean manualEnlistment)
at System.Data.OracleClient.OracleInternalConnection.EnlistTransaction(Transaction transaction)
at System.Data.OracleClient.OracleConnection.EnlistTransaction(Transaction transaction)
at OracleEnlistmentTest.myEnlistmentClass.Prepare(PreparingEnlistment preparingEnlistment) in D:\Projects\Test\DatabaseEnlistmentTest\OracleEnlistmentTest\Program.cs:line 46
at System.Transactions.VolatileEnlistmentPreparing.EnterState(InternalEnlistment enlistment)
at System.Transactions.VolatileEnlistmentActive.ChangeStatePreparing(InternalEnlistment enlistment)
at System.Transactions.TransactionStatePhase0.EnterState(InternalTransaction tx)
at System.Transactions.TransactionStateActive.BeginCommit(InternalTransaction tx, Boolean asyncCommit, AsyncCallback asyncCallback, Object asyncState)
at System.Transactions.CommittableTransaction.Commit()
at OracleEnlistmentTest.Program.Main(String[] args) in D:\Projects\Test\DatabaseEnlistmentTest\OracleEnlistmentTest\Program.cs:line 18
at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

Regards,

Michael


 
 
Sarah Parra - MSFT





PostPosted: .NET Framework Data Access and Storage, Enlisting an Oracle database connection throws an AccessViolationException Top

Thanks, with the updated code you posted I can reproduce the problem. I will investigate this further and post back here once I have more information.

Thanks,
Sarah



 
 
Sarah Parra - MSFT





PostPosted: .NET Framework Data Access and Storage, Enlisting an Oracle database connection throws an AccessViolationException Top

I have investigated this, and it appears to be a bug in Oracle's ORAMTS10.DLL, which is used by System.Data.OracleClient under the covers. As part of enlisting the connection, we make a call to the OCI function OraMTSEnlCtxGet. The third parameter to this function is the service name. ( http://www.hide-link.com/ ) We are passing the long data source part of the connection string, just as it was passed from the client. What I have found is that oramts10 seems to be writing off the end of a string buffer, and thus corrupting memory and causing the crash. When I looked at this under the de****, I found that the actual native access violation was happening in different places every time. To make this more consistent, I used the gflags tool to turn on pageheap ( http://www.hide-link.com/ ;en-us;286470). This makes it more likely that the crash will occur at the actual source of the problem, rather than some later point in time when the corrupted memory is used.

I tried to reproduce the same behavior with Oracle's ODP.NET beta provider that implements .NET 2.0 functionality, but it seems the version I downloaded doesn't implement EnlistTransaction yet, so I couldn't test it. Even if it did implement it, it might not use OraMTSEnlCtxGet in the same way.

My suggestion is for you to contact Oracle and provide this simple repro. Also, here is a call stack of the failure with pageheap turned on, including the native call that's actually failing:

KERNEL32!lstrcpyA(char * lpString1 = 0x0e9a0fd8 "(DESCRIPTION = (ADDRESS_LIST = (ADDRESS ", char * lpString2 = 0x02e5fc88 "(DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = xx.xxx.xx.xx)(PORT = 1521)))(CONNECT_DATA = (SERVICE_NAME = xxxxx)))")+0x18
ORAMTS10!kpntrtyp::kpntrtyp+0xbc
ORAMTS10!kpntenlistctxget+0xa9
oramts!kpntenlistctxget+0x25
System::Data::OracleClient::TracedNativeMethods::OraMTSEnlCtxGet+0x73
System::Data::OracleClient::OciEnlistContext::.ctor+0x65
System::Data::OracleClient::OracleInternalConnection::Enlist+0xe7
System::Data::OracleClient::OracleInternalConnection::EnlistTransaction+0x28
System::Data::OracleClient::OracleConnection::EnlistTransaction+0x58
OracleConsoleApp.myEnlistmentClass.Prepare(System.Transactions.PreparingEnlistment)+0x8f
System.Transactions.VolatileEnlistmentPreparing.EnterState(...)+0xc5
System.Transactions.VolatileEnlistmentActive.ChangeStatePreparing(...)+0x11
System.Transactions.TransactionStatePhase0.EnterState(...)+0x5f
System.Transactions.TransactionStateActive.BeginCommit(...)+0x30
System.Transactions.CommittableTransaction.Commit()+0xd3
OracleConsoleApp.Program.Main(System.String[])+0xd2

It looks like the buffer for lpString1 is only 40 bytes, so the first 40 bytes are copied into it, then a failure occurs when the code attempts to access unavailable memory. You can view this for yourself if you download the debugging tools for Windows: http://www.hide-link.com/ . This will give you both gflags and the WinDbg de**** that you can use to get the above.

Thanks,
Sarah



 
 
msp1502





PostPosted: .NET Framework Data Access and Storage, Enlisting an Oracle database connection throws an AccessViolationException Top

Hi Sarah,

thank you very much for your investigation and for your hint regarding the string buffer of 40 bytes.
As workaround I will not resolve the data source in my connection strings (the goal was to distribute Oracle applications without using tnsnames.ora).

Thanks,
Michael