#pragma warning disable 1634, 1691 //------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ #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 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; /// /// Enables the adding of this service programmatically. /// /// /// public SharedConnectionWorkflowCommitWorkBatchService(string connectionString) { if (String.IsNullOrEmpty(connectionString)) throw new ArgumentNullException("connectionString", ExecutionStringManager.MissingConnectionString); this.unvalidatedConnectionString = connectionString; } /// /// 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. /// /// /// 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(); // // 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 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(); } } } } /// /// Get the SharedConnectionInfo object from the hashtable keyed by the transaction /// /// /// 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 } }