File: System\Transactions\DtcProxyShim\EnlistmentNotifyShim.cs
Web Access
Project: src\src\runtime\src\libraries\System.Transactions.Local\src\System.Transactions.Local.csproj (System.Transactions.Local)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices.Marshalling;
using System.Threading;
using System.Transactions.DtcProxyShim.DtcInterfaces;
using System.Transactions.Oletx;

namespace System.Transactions.DtcProxyShim;

[GeneratedComClass]
internal sealed partial class EnlistmentNotifyShim : NotificationShimBase, ITransactionResourceAsync
{
    internal ITransactionEnlistmentAsync? EnlistmentAsync;

    // MSDTCPRX behaves unpredictably in that if the TM is down when we vote
    // no it will send an AbortRequest.  However if the TM does not go down
    // the enlistment is not go down the AbortRequest is not sent.  This
    // makes reliable cleanup a problem.  To work around this the enlisment
    // shim will eat the AbortRequest if it knows that it has voted No.

    // On Win2k this same problem applies to responding Committed to a
    // single phase commit request.
    private bool _ignoreSpuriousProxyNotifications;

    internal EnlistmentNotifyShim(DtcProxyShimFactory shimFactory, OletxEnlistment enlistmentIdentifier)
        : base(shimFactory, enlistmentIdentifier)
    {
        _ignoreSpuriousProxyNotifications = false;
    }

    internal void SetIgnoreSpuriousProxyNotifications()
        => _ignoreSpuriousProxyNotifications = true;

    public void PrepareRequest(bool fRetaining, OletxXactRm grfRM, bool fWantMoniker, bool fSinglePhase)
    {
        ITransactionEnlistmentAsync? pEnlistmentAsync = Interlocked.Exchange(ref EnlistmentAsync, null);

        if (pEnlistmentAsync is null)
        {
            throw new InvalidOperationException("Unexpected null in pEnlistmentAsync");
        }

        var pPrepareInfo = (IPrepareInfo)pEnlistmentAsync;
        pPrepareInfo.GetPrepareInfoSize(out uint prepareInfoLength);
        var prepareInfoBuffer = new byte[prepareInfoLength];
        pPrepareInfo.GetPrepareInfo(prepareInfoBuffer);

        PrepareInfo = prepareInfoBuffer;
        IsSinglePhase = fSinglePhase;
        NotificationType = ShimNotificationType.PrepareRequestNotify;
        ShimFactory.NewNotification(this);
    }

    public void CommitRequest(OletxXactRm grfRM, IntPtr pNewUOW)
    {
        NotificationType = ShimNotificationType.CommitRequestNotify;
        ShimFactory.NewNotification(this);
    }

    public void AbortRequest(IntPtr pboidReason, bool fRetaining, IntPtr pNewUOW)
    {
        if (!_ignoreSpuriousProxyNotifications)
        {
            // Only create the notification if we have not already voted.
            NotificationType = ShimNotificationType.AbortRequestNotify;
            ShimFactory.NewNotification(this);
        }
    }

    public void TMDown()
    {
        NotificationType = ShimNotificationType.ResourceManagerTmDownNotify;
        ShimFactory.NewNotification(this);
    }
}