405 lines
13 KiB
C#
405 lines
13 KiB
C#
|
//-----------------------------------------------------------------------------
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
namespace System.ServiceModel.ComIntegration
|
||
|
{
|
||
|
using System;
|
||
|
using System.ServiceModel;
|
||
|
using System.Transactions;
|
||
|
using System.Diagnostics;
|
||
|
using System.ServiceModel.Diagnostics;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using SR = System.ServiceModel.SR;
|
||
|
|
||
|
class TransactionProxyBuilder : IProxyCreator
|
||
|
{
|
||
|
ComProxy comProxy = null;
|
||
|
TransactionProxy txProxy = null;
|
||
|
|
||
|
private TransactionProxyBuilder(TransactionProxy proxy)
|
||
|
{
|
||
|
this.txProxy = proxy;
|
||
|
}
|
||
|
void IDisposable.Dispose()
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
ComProxy IProxyCreator.CreateProxy(IntPtr outer, ref Guid riid)
|
||
|
{
|
||
|
if ((riid != typeof(ITransactionProxy).GUID))
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidCastException(SR.GetString(SR.NoInterface, riid)));
|
||
|
if (outer == IntPtr.Zero)
|
||
|
{
|
||
|
// transactions require failfasts to prevent corruption
|
||
|
DiagnosticUtility.FailFast("OuterProxy cannot be null");
|
||
|
}
|
||
|
|
||
|
if (comProxy == null)
|
||
|
{
|
||
|
comProxy = ComProxy.Create(outer, txProxy, null);
|
||
|
return comProxy;
|
||
|
|
||
|
}
|
||
|
else
|
||
|
return comProxy.Clone();
|
||
|
}
|
||
|
|
||
|
bool IProxyCreator.SupportsErrorInfo(ref Guid riid)
|
||
|
{
|
||
|
if ((riid != typeof(ITransactionProxy).GUID))
|
||
|
return false;
|
||
|
else
|
||
|
return true;
|
||
|
|
||
|
}
|
||
|
|
||
|
bool IProxyCreator.SupportsDispatch()
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool IProxyCreator.SupportsIntrinsics()
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public static IntPtr CreateTransactionProxyTearOff(TransactionProxy txProxy)
|
||
|
{
|
||
|
IProxyCreator txProxyBuilder = new TransactionProxyBuilder(txProxy);
|
||
|
IProxyManager proxyManager = new ProxyManager(txProxyBuilder);
|
||
|
Guid iid = typeof(ITransactionProxy).GUID;
|
||
|
return OuterProxyWrapper.CreateOuterProxyInstance(proxyManager, ref iid);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
class TransactionProxy : ITransactionProxy,
|
||
|
IExtension<InstanceContext>
|
||
|
{
|
||
|
Transaction currentTransaction;
|
||
|
VoterBallot currentVoter;
|
||
|
object syncRoot;
|
||
|
Guid appid;
|
||
|
Guid clsid;
|
||
|
int instanceID = 0;
|
||
|
|
||
|
public TransactionProxy(Guid appid, Guid clsid)
|
||
|
{
|
||
|
this.syncRoot = new object();
|
||
|
this.appid = appid;
|
||
|
this.clsid = clsid;
|
||
|
}
|
||
|
|
||
|
public Transaction CurrentTransaction
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.currentTransaction;
|
||
|
}
|
||
|
}
|
||
|
public Guid AppId
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.appid;
|
||
|
}
|
||
|
}
|
||
|
public Guid Clsid
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.clsid;
|
||
|
}
|
||
|
}
|
||
|
public int InstanceID
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.instanceID;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.instanceID = value;
|
||
|
}
|
||
|
}
|
||
|
public void SetTransaction(Transaction transaction)
|
||
|
{
|
||
|
lock (this.syncRoot)
|
||
|
{
|
||
|
if (transaction == null)
|
||
|
{
|
||
|
// transactions require failfasts to prevent corruption
|
||
|
DiagnosticUtility.FailFast("Attempting to set transaction to NULL");
|
||
|
}
|
||
|
|
||
|
if (this.currentTransaction == null)
|
||
|
{
|
||
|
ProxyEnlistment enlistment;
|
||
|
enlistment = new ProxyEnlistment(this, transaction);
|
||
|
transaction.EnlistVolatile(enlistment, EnlistmentOptions.None);
|
||
|
this.currentTransaction = transaction;
|
||
|
if (this.currentVoter != null)
|
||
|
{
|
||
|
this.currentVoter.SetTransaction(this.currentTransaction);
|
||
|
}
|
||
|
}
|
||
|
else if (this.currentTransaction != transaction)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(Error.TransactionMismatch());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// IExtension<ServiceInstance>
|
||
|
public void Attach(InstanceContext owner) { }
|
||
|
public void Detach(InstanceContext owner) { }
|
||
|
|
||
|
// ITransactionProxy
|
||
|
public void Commit(Guid guid)
|
||
|
{
|
||
|
// transactions require failfasts to prevent corruption
|
||
|
DiagnosticUtility.FailFast("Commit not supported: BYOT only!");
|
||
|
}
|
||
|
|
||
|
public void Abort()
|
||
|
{
|
||
|
if (this.currentTransaction != null)
|
||
|
{
|
||
|
this.currentTransaction.Rollback();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public IDtcTransaction Promote()
|
||
|
{
|
||
|
EnsureTransaction();
|
||
|
return TransactionInterop.GetDtcTransaction(
|
||
|
this.currentTransaction);
|
||
|
}
|
||
|
|
||
|
public void CreateVoter(
|
||
|
ITransactionVoterNotifyAsync2 voterNotification,
|
||
|
IntPtr voterBallot)
|
||
|
{
|
||
|
if (IntPtr.Zero == voterBallot)
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("voterBallot");
|
||
|
|
||
|
lock (this.syncRoot)
|
||
|
{
|
||
|
if (this.currentVoter != null)
|
||
|
{
|
||
|
// transactions require failfasts to prevent corruption
|
||
|
DiagnosticUtility.FailFast("Assumption: proxy only needs one voter");
|
||
|
}
|
||
|
|
||
|
VoterBallot voter = new VoterBallot(voterNotification, this);
|
||
|
if (this.currentTransaction != null)
|
||
|
{
|
||
|
voter.SetTransaction(this.currentTransaction);
|
||
|
}
|
||
|
|
||
|
this.currentVoter = voter;
|
||
|
|
||
|
IntPtr ppv = InterfaceHelper.GetInterfacePtrForObject(typeof(ITransactionVoterBallotAsync2).GUID, this.currentVoter);
|
||
|
|
||
|
Marshal.WriteIntPtr(voterBallot, ppv);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public DtcIsolationLevel GetIsolationLevel()
|
||
|
{
|
||
|
DtcIsolationLevel retVal;
|
||
|
switch (this.currentTransaction.IsolationLevel)
|
||
|
{
|
||
|
case IsolationLevel.Serializable:
|
||
|
retVal = DtcIsolationLevel.ISOLATIONLEVEL_SERIALIZABLE;
|
||
|
break;
|
||
|
case IsolationLevel.RepeatableRead:
|
||
|
retVal = DtcIsolationLevel.ISOLATIONLEVEL_REPEATABLEREAD;
|
||
|
break;
|
||
|
case IsolationLevel.ReadCommitted:
|
||
|
retVal = DtcIsolationLevel.ISOLATIONLEVEL_READCOMMITTED;
|
||
|
break;
|
||
|
case IsolationLevel.ReadUncommitted:
|
||
|
retVal = DtcIsolationLevel.ISOLATIONLEVEL_READUNCOMMITTED;
|
||
|
break;
|
||
|
default:
|
||
|
retVal = DtcIsolationLevel.ISOLATIONLEVEL_SERIALIZABLE;
|
||
|
break;
|
||
|
}
|
||
|
return retVal;
|
||
|
}
|
||
|
|
||
|
public Guid GetIdentifier()
|
||
|
{
|
||
|
return this.currentTransaction.TransactionInformation.DistributedIdentifier;
|
||
|
}
|
||
|
|
||
|
// ITransactionProxy2
|
||
|
public bool IsReusable()
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void ClearTransaction(ProxyEnlistment enlistment)
|
||
|
{
|
||
|
lock (this.syncRoot)
|
||
|
{
|
||
|
if (this.currentTransaction == null)
|
||
|
{
|
||
|
// transactions require failfasts to prevent corruption
|
||
|
DiagnosticUtility.FailFast("Clearing inactive TransactionProxy");
|
||
|
}
|
||
|
if (enlistment.Transaction != this.currentTransaction)
|
||
|
{
|
||
|
// transactions require failfasts to prevent corruption
|
||
|
DiagnosticUtility.FailFast("Incorrectly working on multiple transactions");
|
||
|
}
|
||
|
this.currentTransaction = null;
|
||
|
this.currentVoter = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void EnsureTransaction()
|
||
|
{
|
||
|
lock (this.syncRoot)
|
||
|
{
|
||
|
if (this.currentTransaction == null)
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new COMException(null, HR.CONTEXT_E_NOTRANSACTION));
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class ProxyEnlistment : IEnlistmentNotification
|
||
|
{
|
||
|
TransactionProxy proxy;
|
||
|
Transaction transaction;
|
||
|
|
||
|
public ProxyEnlistment(TransactionProxy proxy,
|
||
|
Transaction transaction)
|
||
|
{
|
||
|
this.proxy = proxy;
|
||
|
this.transaction = transaction;
|
||
|
}
|
||
|
|
||
|
public Transaction Transaction
|
||
|
{
|
||
|
get { return this.transaction; }
|
||
|
}
|
||
|
|
||
|
public void Prepare(PreparingEnlistment preparingEnlistment)
|
||
|
{
|
||
|
this.proxy.ClearTransaction(this);
|
||
|
this.proxy = null;
|
||
|
preparingEnlistment.Done();
|
||
|
|
||
|
}
|
||
|
|
||
|
public void Commit(Enlistment enlistment)
|
||
|
{
|
||
|
// transactions require failfasts to prevent corruption
|
||
|
DiagnosticUtility.FailFast("Should have voted read only");
|
||
|
}
|
||
|
|
||
|
public void Rollback(Enlistment enlistment)
|
||
|
{
|
||
|
this.proxy.ClearTransaction(this);
|
||
|
this.proxy = null;
|
||
|
enlistment.Done();
|
||
|
}
|
||
|
|
||
|
public void InDoubt(Enlistment enlistment)
|
||
|
{
|
||
|
// transactions require failfasts to prevent corruption
|
||
|
DiagnosticUtility.FailFast("Should have voted read only");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class VoterBallot : ITransactionVoterBallotAsync2, IEnlistmentNotification
|
||
|
{
|
||
|
const int S_OK = 0;
|
||
|
|
||
|
ITransactionVoterNotifyAsync2 notification;
|
||
|
Transaction transaction;
|
||
|
Enlistment enlistment;
|
||
|
PreparingEnlistment preparingEnlistment;
|
||
|
TransactionProxy proxy;
|
||
|
|
||
|
public VoterBallot(ITransactionVoterNotifyAsync2 notification, TransactionProxy proxy)
|
||
|
{
|
||
|
this.notification = notification;
|
||
|
this.proxy = proxy;
|
||
|
}
|
||
|
|
||
|
public void SetTransaction(Transaction transaction)
|
||
|
{
|
||
|
if (this.transaction != null)
|
||
|
{
|
||
|
// transactions require failfasts to prevent corruption
|
||
|
DiagnosticUtility.FailFast("Already have a transaction in the ballot!");
|
||
|
}
|
||
|
this.transaction = transaction;
|
||
|
this.enlistment = transaction.EnlistVolatile(
|
||
|
this,
|
||
|
EnlistmentOptions.None);
|
||
|
}
|
||
|
|
||
|
|
||
|
public void Prepare(PreparingEnlistment enlistment)
|
||
|
{
|
||
|
this.preparingEnlistment = enlistment;
|
||
|
this.notification.VoteRequest();
|
||
|
}
|
||
|
|
||
|
public void Rollback(Enlistment enlistment)
|
||
|
{
|
||
|
enlistment.Done();
|
||
|
this.notification.Aborted(0, false, 0, S_OK);
|
||
|
ComPlusTxProxyTrace.Trace(TraceEventType.Verbose, TraceCode.ComIntegrationTxProxyTxAbortedByTM,
|
||
|
SR.TraceCodeComIntegrationTxProxyTxAbortedByTM, proxy.AppId, proxy.Clsid, transaction.TransactionInformation.DistributedIdentifier, proxy.InstanceID);
|
||
|
Marshal.ReleaseComObject(this.notification);
|
||
|
this.notification = null;
|
||
|
}
|
||
|
|
||
|
public void Commit(Enlistment enlistment)
|
||
|
{
|
||
|
enlistment.Done();
|
||
|
this.notification.Committed(false, 0, S_OK);
|
||
|
ComPlusTxProxyTrace.Trace(TraceEventType.Verbose, TraceCode.ComIntegrationTxProxyTxCommitted,
|
||
|
SR.TraceCodeComIntegrationTxProxyTxCommitted, proxy.AppId, proxy.Clsid, transaction.TransactionInformation.DistributedIdentifier, proxy.InstanceID);
|
||
|
Marshal.ReleaseComObject(this.notification);
|
||
|
this.notification = null;
|
||
|
}
|
||
|
|
||
|
public void InDoubt(Enlistment enlistment)
|
||
|
{
|
||
|
enlistment.Done();
|
||
|
this.notification.InDoubt();
|
||
|
Marshal.ReleaseComObject(this.notification);
|
||
|
this.notification = null;
|
||
|
}
|
||
|
|
||
|
public void VoteRequestDone(int hr, int reason)
|
||
|
{
|
||
|
if (this.preparingEnlistment == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
|
||
|
SR.GetString(SR.NoVoteIssued)));
|
||
|
}
|
||
|
|
||
|
if (S_OK == hr)
|
||
|
{
|
||
|
this.preparingEnlistment.Prepared();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
|
||
|
this.preparingEnlistment.ForceRollback();
|
||
|
ComPlusTxProxyTrace.Trace(TraceEventType.Verbose, TraceCode.ComIntegrationTxProxyTxAbortedByContext,
|
||
|
SR.TraceCodeComIntegrationTxProxyTxAbortedByContext, proxy.AppId, proxy.Clsid, transaction.TransactionInformation.DistributedIdentifier, proxy.InstanceID);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|