296 lines
12 KiB
C#
296 lines
12 KiB
C#
|
#pragma warning disable 1634, 1691
|
||
|
//------------------------------------------------------------------------------
|
||
|
// <copyright file="SharedConnectionWorkflowTransactionService.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
#region Using directives
|
||
|
|
||
|
using System;
|
||
|
using System.Transactions;
|
||
|
using System.Diagnostics;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Collections.Specialized;
|
||
|
using System.Configuration;
|
||
|
using System.Threading;
|
||
|
#endregion
|
||
|
|
||
|
namespace System.Workflow.Runtime.Hosting
|
||
|
{
|
||
|
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||
|
public class SharedConnectionWorkflowCommitWorkBatchService : WorkflowCommitWorkBatchService
|
||
|
{
|
||
|
private DbResourceAllocator dbResourceAllocator;
|
||
|
private IDictionary<Transaction, SharedConnectionInfo> transactionConnectionTable;
|
||
|
private object tableSyncObject = new object();
|
||
|
|
||
|
// Saved from constructor input to be used in service start initialization
|
||
|
private NameValueCollection configParameters;
|
||
|
string unvalidatedConnectionString;
|
||
|
private bool _enableRetries = false;
|
||
|
private bool _ignoreCommonEnableRetries = false;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Enables the adding of this service programmatically.
|
||
|
/// </summary>
|
||
|
/// <param name="runtime"></param>
|
||
|
/// <param name="connectionString"></param>
|
||
|
public SharedConnectionWorkflowCommitWorkBatchService(string connectionString)
|
||
|
{
|
||
|
if (String.IsNullOrEmpty(connectionString))
|
||
|
throw new ArgumentNullException("connectionString", ExecutionStringManager.MissingConnectionString);
|
||
|
|
||
|
this.unvalidatedConnectionString = connectionString;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Enables adding of this service from a config file.
|
||
|
/// Get the connection string from the runtime common parameter section or the particular service parameter section
|
||
|
/// of the configuration file, and instantiate a DbResourceAllocator object.
|
||
|
/// </summary>
|
||
|
/// <param name="runtime"></param>
|
||
|
/// <param name="parameters"></param>
|
||
|
public SharedConnectionWorkflowCommitWorkBatchService(NameValueCollection parameters)
|
||
|
{
|
||
|
if (parameters == null)
|
||
|
throw new ArgumentNullException("parameters", ExecutionStringManager.MissingParameters);
|
||
|
|
||
|
if (parameters.Count > 0)
|
||
|
{
|
||
|
foreach (string key in parameters.Keys)
|
||
|
{
|
||
|
if (0 == string.Compare("EnableRetries", key, StringComparison.OrdinalIgnoreCase))
|
||
|
{
|
||
|
_enableRetries = bool.Parse(parameters[key]);
|
||
|
_ignoreCommonEnableRetries = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.configParameters = parameters;
|
||
|
}
|
||
|
|
||
|
#region Accessors
|
||
|
internal string ConnectionString
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
#pragma warning disable 56503
|
||
|
if (this.dbResourceAllocator == null)
|
||
|
{
|
||
|
// Other hosting services may try to get the connection string during their initialization phase
|
||
|
// (in WorkflowRuntimeService.Start) to dectect string mismatch conflict
|
||
|
if (this.Runtime == null)
|
||
|
throw new InvalidOperationException(ExecutionStringManager.WorkflowRuntimeNotStarted);
|
||
|
this.dbResourceAllocator = new DbResourceAllocator(this.Runtime, this.configParameters, this.unvalidatedConnectionString);
|
||
|
}
|
||
|
#pragma warning restore 56503
|
||
|
return this.dbResourceAllocator.ConnectionString;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool EnableRetries
|
||
|
{
|
||
|
get { return _enableRetries; }
|
||
|
set
|
||
|
{
|
||
|
_enableRetries = value;
|
||
|
_ignoreCommonEnableRetries = true;
|
||
|
}
|
||
|
}
|
||
|
#endregion Accessors
|
||
|
|
||
|
#region WorkflowRuntimeService
|
||
|
override protected internal void Start()
|
||
|
{
|
||
|
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "SharedConnectionWorkflowCommitWorkBatchService: Starting");
|
||
|
|
||
|
this.dbResourceAllocator = new DbResourceAllocator(this.Runtime, this.configParameters, this.unvalidatedConnectionString);
|
||
|
if (this.transactionConnectionTable == null)
|
||
|
this.transactionConnectionTable = new Dictionary<Transaction, SharedConnectionInfo>();
|
||
|
|
||
|
//
|
||
|
// If we didn't find a local value for enable retries
|
||
|
// check in the common section
|
||
|
if ((!_ignoreCommonEnableRetries) && (null != base.Runtime))
|
||
|
{
|
||
|
NameValueConfigurationCollection commonConfigurationParameters = base.Runtime.CommonParameters;
|
||
|
if (commonConfigurationParameters != null)
|
||
|
{
|
||
|
// Then scan for connection string in the common configuration parameters section
|
||
|
foreach (string key in commonConfigurationParameters.AllKeys)
|
||
|
{
|
||
|
if (string.Compare("EnableRetries", key, StringComparison.OrdinalIgnoreCase) == 0)
|
||
|
{
|
||
|
_enableRetries = bool.Parse(commonConfigurationParameters[key].Value);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
base.Start();
|
||
|
}
|
||
|
|
||
|
protected override void OnStopped()
|
||
|
{
|
||
|
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "SharedConnectionWorkflowCommitWorkBatchService: Stopping");
|
||
|
foreach (KeyValuePair<Transaction, SharedConnectionInfo> kvp in this.transactionConnectionTable)
|
||
|
{
|
||
|
kvp.Value.Dispose();
|
||
|
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Removing transaction " + kvp.Key.GetHashCode());
|
||
|
}
|
||
|
this.transactionConnectionTable.Clear();
|
||
|
this.dbResourceAllocator = null;
|
||
|
base.OnStopped();
|
||
|
}
|
||
|
#endregion Public Methods
|
||
|
|
||
|
#region Public Methods
|
||
|
|
||
|
protected internal override void CommitWorkBatch(WorkflowCommitWorkBatchService.CommitWorkBatchCallback commitWorkBatchCallback)
|
||
|
{
|
||
|
//
|
||
|
// Disable retries by default, reset to allow retries below if we own the tx
|
||
|
DbRetry dbRetry = new DbRetry(_enableRetries);
|
||
|
short retryCounter = dbRetry.MaxRetries;
|
||
|
|
||
|
while (true)
|
||
|
{
|
||
|
//
|
||
|
// When using LocalTransaction handle block access to the connection
|
||
|
// in the transaction event handlers until all IPendingWork members have completed
|
||
|
ManualResetEvent handle = new ManualResetEvent(false);
|
||
|
Transaction tx = null;
|
||
|
SharedConnectionInfo connectionInfo = null;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (null == Transaction.Current)
|
||
|
{
|
||
|
//
|
||
|
// It's OK to retry here as we own the tx
|
||
|
retryCounter = 0;
|
||
|
//
|
||
|
// Create a local, non promotable transaction that we share with our OOB services
|
||
|
tx = new CommittableTransaction();
|
||
|
connectionInfo = new SharedConnectionInfo(this.dbResourceAllocator, tx, false, handle);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// Can't retry as we don't own the tx
|
||
|
// Create a dependent transaction and don't restrict promotion.
|
||
|
tx = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
|
||
|
connectionInfo = new SharedConnectionInfo(this.dbResourceAllocator, tx, true, handle);
|
||
|
}
|
||
|
|
||
|
AddToConnectionInfoTable(tx, connectionInfo);
|
||
|
|
||
|
using (TransactionScope ts = new TransactionScope(tx))
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
commitWorkBatchCallback();
|
||
|
ts.Complete();
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
RemoveConnectionFromInfoTable(tx);
|
||
|
//
|
||
|
// Unblock transaction event handlers
|
||
|
handle.Set();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CommittableTransaction committableTransaction = tx as CommittableTransaction;
|
||
|
if (committableTransaction != null)
|
||
|
committableTransaction.Commit();
|
||
|
|
||
|
DependentTransaction dependentTransaction = tx as DependentTransaction;
|
||
|
if (dependentTransaction != null)
|
||
|
dependentTransaction.Complete();
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
tx.Rollback();
|
||
|
|
||
|
WorkflowTrace.Host.TraceEvent(TraceEventType.Error, 0, "SharedConnectionWorkflowCommitWorkBatchService caught exception from commitWorkBatchCallback: " + e.ToString());
|
||
|
|
||
|
if (dbRetry.TryDoRetry(ref retryCounter))
|
||
|
{
|
||
|
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "SharedConnectionWorkflowCommitWorkBatchService retrying commitWorkBatchCallback (retry attempt " + retryCounter.ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
|
||
|
continue;
|
||
|
}
|
||
|
else
|
||
|
throw;
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
handle.Close();
|
||
|
if (tx != null)
|
||
|
{
|
||
|
tx.Dispose();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// Get the SharedConnectionInfo object from the hashtable keyed by the transaction
|
||
|
/// </summary>
|
||
|
/// <param name="transaction"></param>
|
||
|
/// <returns></returns>
|
||
|
internal SharedConnectionInfo GetConnectionInfo(Transaction transaction)
|
||
|
{
|
||
|
return LookupConnectionInfoTable(transaction);
|
||
|
}
|
||
|
|
||
|
#endregion Public Methods
|
||
|
|
||
|
#region Private Methods
|
||
|
|
||
|
private void RemoveConnectionFromInfoTable(Transaction transaction)
|
||
|
{
|
||
|
lock (this.tableSyncObject)
|
||
|
{
|
||
|
SharedConnectionInfo connectionInfo;
|
||
|
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "TransactionCompleted " + transaction.GetHashCode());
|
||
|
if (transactionConnectionTable.TryGetValue(transaction, out connectionInfo))
|
||
|
{
|
||
|
connectionInfo.Dispose();
|
||
|
this.transactionConnectionTable.Remove(transaction);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "TransactionCompleted " + transaction.GetHashCode() +
|
||
|
" not found in table of count " + this.transactionConnectionTable.Count);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void AddToConnectionInfoTable(Transaction transaction, SharedConnectionInfo connectionInfo)
|
||
|
{
|
||
|
lock (this.tableSyncObject)
|
||
|
{
|
||
|
this.transactionConnectionTable.Add(transaction, connectionInfo);
|
||
|
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "AddToConnectionInfoTable " + transaction.GetHashCode() +
|
||
|
" in table of count " + this.transactionConnectionTable.Count);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private SharedConnectionInfo LookupConnectionInfoTable(Transaction transaction)
|
||
|
{
|
||
|
lock (this.tableSyncObject)
|
||
|
{
|
||
|
return transactionConnectionTable[transaction];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion Private Methods
|
||
|
}
|
||
|
}
|