You've already forked linux-packaging-mono
Imported Upstream version 4.6.0.125
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
This commit is contained in:
parent
a569aebcfd
commit
e79aa3c0ed
@@ -0,0 +1,380 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="DbResourceAllocator.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Configuration;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.OleDb;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Transactions;
|
||||
using System.Threading;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Local database providers we support
|
||||
/// </summary>
|
||||
internal enum Provider
|
||||
{
|
||||
SqlClient = 0,
|
||||
OleDB = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal Database access abstraction to
|
||||
/// - abstract the derived Out-of-box SharedConnectionInfo from all DB hosting services
|
||||
/// - provide uniform connection string management
|
||||
/// - and support different database providers
|
||||
/// </summary>
|
||||
internal sealed class DbResourceAllocator
|
||||
{
|
||||
const string EnlistFalseToken = ";Enlist=false";
|
||||
internal const string ConnectionStringToken = "ConnectionString";
|
||||
|
||||
string connString;
|
||||
Provider localProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the object by getting the connection string from the parameter or
|
||||
/// out of the configuration settings
|
||||
/// </summary>
|
||||
/// <param name="runtime"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <param name="connectionString"></param>
|
||||
internal DbResourceAllocator(
|
||||
WorkflowRuntime runtime,
|
||||
NameValueCollection parameters,
|
||||
string connectionString)
|
||||
{
|
||||
// If connection string not specified in input, search the config sections
|
||||
if (String.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
if (parameters != null)
|
||||
{
|
||||
// First search in this service's parameters
|
||||
foreach (string key in parameters.AllKeys)
|
||||
{
|
||||
if (string.Compare(ConnectionStringToken, key, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
connectionString = parameters[ConnectionStringToken];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (String.IsNullOrEmpty(connectionString) && (runtime != null))
|
||||
{
|
||||
NameValueConfigurationCollection commonConfigurationParameters = 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(ConnectionStringToken, key, StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
connectionString = commonConfigurationParameters[ConnectionStringToken].Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no connectionString parsed out of the params, inner layer throws
|
||||
// System.ArgumentNullException: Connection string cannot be null or empty
|
||||
// Parameter name: connectionString
|
||||
// But this API caller does not have connectionString param.
|
||||
// So throw ArgumentException with the original message.
|
||||
if (String.IsNullOrEmpty(connectionString))
|
||||
throw new ArgumentNullException(ConnectionStringToken, ExecutionStringManager.MissingConnectionString);
|
||||
}
|
||||
|
||||
Init(connectionString);
|
||||
}
|
||||
|
||||
#region Accessors
|
||||
|
||||
internal string ConnectionString
|
||||
{
|
||||
get { return this.connString; }
|
||||
}
|
||||
|
||||
#endregion Accessors
|
||||
|
||||
|
||||
#region Internal Methods
|
||||
/// <summary>
|
||||
/// Disallow the hosting service to have different connection string if using SharedConnectionWorkflowTransactionService
|
||||
/// Should be called after all hosting services are added to the WorkflowRuntime
|
||||
/// </summary>
|
||||
/// <param name="transactionService"></param>
|
||||
internal void DetectSharedConnectionConflict(WorkflowCommitWorkBatchService transactionService)
|
||||
{
|
||||
SharedConnectionWorkflowCommitWorkBatchService sharedConnectionTransactionService = transactionService as SharedConnectionWorkflowCommitWorkBatchService;
|
||||
if (sharedConnectionTransactionService != null)
|
||||
{
|
||||
if (String.Compare(sharedConnectionTransactionService.ConnectionString, this.connString, StringComparison.Ordinal) != 0)
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
|
||||
ExecutionStringManager.SharedConnectionStringSpecificationConflict, this.connString, sharedConnectionTransactionService.ConnectionString));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region Get a connection
|
||||
|
||||
internal DbConnection OpenNewConnection()
|
||||
{
|
||||
// Always disallow AutoEnlist since we enlist explicitly when necessary
|
||||
return OpenNewConnection(true);
|
||||
}
|
||||
|
||||
internal DbConnection OpenNewConnectionNoEnlist()
|
||||
{
|
||||
return OpenNewConnection(true);
|
||||
}
|
||||
|
||||
internal DbConnection OpenNewConnection(bool disallowEnlist)
|
||||
{
|
||||
DbConnection connection = null;
|
||||
string connectionStr = this.connString;
|
||||
|
||||
if (disallowEnlist)
|
||||
connectionStr += DbResourceAllocator.EnlistFalseToken;
|
||||
|
||||
if (this.localProvider == Provider.SqlClient)
|
||||
connection = new SqlConnection(connectionStr);
|
||||
else
|
||||
connection = new OleDbConnection(connectionStr);
|
||||
|
||||
connection.Open();
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a connection enlisted to the transaction.
|
||||
/// If the transaction already has a connection attached to it, we return that,
|
||||
/// otherwise we create a new connection and enlist to the transaction
|
||||
/// </summary>
|
||||
/// <param name="transaction"></param>
|
||||
/// <param name="isNewConnection">output if we created a connection</param>
|
||||
/// <returns></returns>
|
||||
internal DbConnection GetEnlistedConnection(WorkflowCommitWorkBatchService txSvc, Transaction transaction, out bool isNewConnection)
|
||||
{
|
||||
DbConnection connection;
|
||||
SharedConnectionInfo connectionInfo = GetConnectionInfo(txSvc, transaction);
|
||||
|
||||
if (connectionInfo != null)
|
||||
{
|
||||
connection = connectionInfo.DBConnection;
|
||||
Debug.Assert((connection != null), "null connection");
|
||||
Debug.Assert((connection.State == System.Data.ConnectionState.Open),
|
||||
"Invalid connection state " + connection.State + " for connection " + connection);
|
||||
|
||||
isNewConnection = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
connection = this.OpenNewConnection();
|
||||
connection.EnlistTransaction(transaction);
|
||||
|
||||
isNewConnection = true;
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
#endregion Get a connection
|
||||
|
||||
#region Get Local Transaction
|
||||
|
||||
internal static DbTransaction GetLocalTransaction(WorkflowCommitWorkBatchService txSvc, Transaction transaction)
|
||||
{
|
||||
DbTransaction localTransaction = null;
|
||||
SharedConnectionInfo connectionInfo = GetConnectionInfo(txSvc, transaction);
|
||||
|
||||
if (connectionInfo != null)
|
||||
localTransaction = connectionInfo.DBTransaction;
|
||||
|
||||
return localTransaction;
|
||||
}
|
||||
|
||||
#endregion Get Local Transaction
|
||||
|
||||
#region Get a command object for querying
|
||||
|
||||
internal DbCommand NewCommand()
|
||||
{
|
||||
DbConnection dbConnection = OpenNewConnection();
|
||||
return DbResourceAllocator.NewCommand(dbConnection);
|
||||
}
|
||||
|
||||
internal static DbCommand NewCommand(DbConnection dbConnection)
|
||||
{
|
||||
return NewCommand(null, dbConnection, null);
|
||||
}
|
||||
internal static DbCommand NewCommand(string commandText, DbConnection dbConnection, DbTransaction transaction)
|
||||
{
|
||||
DbCommand command = dbConnection.CreateCommand();
|
||||
command.CommandText = commandText;
|
||||
command.Transaction = transaction;
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
#endregion Get a command object for querying
|
||||
|
||||
#region build a command parameter object for a stored procedure
|
||||
|
||||
internal DbParameter NewDbParameter()
|
||||
{
|
||||
return NewDbParameter(null, null);
|
||||
}
|
||||
|
||||
internal DbParameter NewDbParameter(string parameterName, DbType type)
|
||||
{
|
||||
if (this.localProvider == Provider.SqlClient)
|
||||
{
|
||||
if (type == DbType.Int64)
|
||||
return new SqlParameter(parameterName, SqlDbType.BigInt);
|
||||
else
|
||||
return new SqlParameter(parameterName, type);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (type == DbType.Int64)
|
||||
return new OleDbParameter(parameterName, OleDbType.BigInt);
|
||||
else
|
||||
return new OleDbParameter(parameterName, type);
|
||||
}
|
||||
}
|
||||
|
||||
internal DbParameter NewDbParameter(string parameterName, DbType type, ParameterDirection direction)
|
||||
{
|
||||
DbParameter parameter = NewDbParameter(parameterName, type);
|
||||
parameter.Direction = direction;
|
||||
|
||||
return parameter;
|
||||
}
|
||||
|
||||
internal DbParameter NewDbParameter(string parameterName, object value)
|
||||
{
|
||||
if (this.localProvider == Provider.SqlClient)
|
||||
return new SqlParameter(parameterName, value);
|
||||
else
|
||||
return new OleDbParameter(parameterName, value);
|
||||
}
|
||||
|
||||
#endregion build a command parameter object for a stored procedure
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
|
||||
#region Private Helpers
|
||||
|
||||
private void Init(string connectionStr)
|
||||
{
|
||||
SetConnectionString(connectionStr);
|
||||
|
||||
try
|
||||
{
|
||||
// Open a connection to see if it's a valid connection string
|
||||
using (DbConnection connection = this.OpenNewConnection(false))
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new ArgumentException(ExecutionStringManager.InvalidDbConnection, "connectionString", e);
|
||||
}
|
||||
|
||||
// OLEDB connection pooling causes this exception in ExecuteInsertWorkflowInstance
|
||||
// "Cannot start more transactions on this session."
|
||||
// Disable pooling to avoid dirty connections.
|
||||
if (this.localProvider == Provider.OleDB)
|
||||
this.connString = String.Concat(this.connString, ";OLE DB Services=-4");
|
||||
}
|
||||
|
||||
private void SetConnectionString(string connectionString)
|
||||
{
|
||||
if (String.IsNullOrEmpty(connectionString) || String.IsNullOrEmpty(connectionString.Trim()))
|
||||
throw new ArgumentNullException("connectionString", ExecutionStringManager.MissingConnectionString);
|
||||
|
||||
DbConnectionStringBuilder dcsb = new DbConnectionStringBuilder();
|
||||
dcsb.ConnectionString = connectionString;
|
||||
|
||||
// Don't allow the client to specify an auto-enlist value since we decide whether to participate in a transaction
|
||||
// (enlist for writing and not for reading).
|
||||
if (dcsb.ContainsKey("enlist"))
|
||||
{
|
||||
throw new ArgumentException(ExecutionStringManager.InvalidEnlist);
|
||||
}
|
||||
|
||||
this.connString = connectionString;
|
||||
//
|
||||
// We only support sqlclient, sql is the only data store our OOB services talk to.
|
||||
localProvider = Provider.SqlClient;
|
||||
}
|
||||
/*
|
||||
private void SetLocalProvider(string connectionString)
|
||||
{
|
||||
// Assume caller already validated the connection string
|
||||
MatchCollection providers = Regex.Matches(connectionString, @"(^|;)\s*provider\s*=[^;$]*(;|$)", RegexOptions.IgnoreCase);
|
||||
|
||||
// Cannot use DbConnectionStringBuilder because it selects the last provider, not the first one, by itself.
|
||||
// A legal Sql connection string allows for multiple provider specification and
|
||||
// selects the first provider
|
||||
if (providers.Count > 0)
|
||||
{
|
||||
// Check if the first one matches "sqloledb" or "sqloledb.<digit>"
|
||||
if (Regex.IsMatch(providers[0].Value, @"provider\s*=\s*sqloledb(\.\d+)?\s*(;|$)", RegexOptions.IgnoreCase))
|
||||
{
|
||||
this.localProvider = Provider.OleDB;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't support other providers
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,ExecutionStringManager.UnsupportedSqlProvider, providers[0].Value));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// SqlClient provider requires no provider keyword specified in connection string
|
||||
this.localProvider = Provider.SqlClient;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private static SharedConnectionInfo GetConnectionInfo(WorkflowCommitWorkBatchService txSvc, Transaction transaction)
|
||||
{
|
||||
SharedConnectionInfo connectionInfo = null;
|
||||
|
||||
SharedConnectionWorkflowCommitWorkBatchService scTxSvc = txSvc as SharedConnectionWorkflowCommitWorkBatchService;
|
||||
if (scTxSvc != null)
|
||||
{
|
||||
connectionInfo = scTxSvc.GetConnectionInfo(transaction);
|
||||
|
||||
// The transaction service can't find entry if the transaction has been completed.
|
||||
// be sure to propate the error so durable services can cast to appropriate exception
|
||||
if (connectionInfo == null)
|
||||
throw new ArgumentException(
|
||||
String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.InvalidTransaction));
|
||||
}
|
||||
return connectionInfo;
|
||||
}
|
||||
|
||||
#endregion Private Helpers
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#region Imports
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.ComponentModel.Design;
|
||||
using System.ComponentModel.Design.Serialization;
|
||||
using System.Workflow.ComponentModel;
|
||||
using System.Workflow.ComponentModel.Compiler;
|
||||
using System.Workflow.ComponentModel.Serialization;
|
||||
using System.Workflow.ComponentModel.Design;
|
||||
using System.Workflow.Runtime;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public class DefaultWorkflowLoaderService : WorkflowLoaderService
|
||||
{
|
||||
protected internal override Activity CreateInstance(Type workflowType)
|
||||
{
|
||||
if (workflowType == null)
|
||||
throw new ArgumentNullException("workflowType");
|
||||
|
||||
if (!typeof(Activity).IsAssignableFrom(workflowType))
|
||||
throw new ArgumentException(ExecutionStringManager.TypeMustImplementRootActivity);
|
||||
|
||||
if (workflowType.GetConstructor(System.Type.EmptyTypes) == null)
|
||||
throw new ArgumentException(ExecutionStringManager.TypeMustHavePublicDefaultConstructor);
|
||||
|
||||
return Activator.CreateInstance(workflowType) as Activity;
|
||||
}
|
||||
|
||||
// This function will create a new root activity definition tree by deserializing the xoml and the rules file.
|
||||
protected internal override Activity CreateInstance(XmlReader workflowDefinitionReader, XmlReader rulesReader)
|
||||
{
|
||||
if (workflowDefinitionReader == null)
|
||||
throw new ArgumentNullException("workflowDefinitionReader");
|
||||
|
||||
Activity root = null;
|
||||
ValidationErrorCollection errors = new ValidationErrorCollection();
|
||||
ServiceContainer serviceContainer = new ServiceContainer();
|
||||
ITypeProvider typeProvider = this.Runtime.GetService<ITypeProvider>();
|
||||
if (typeProvider != null)
|
||||
serviceContainer.AddService(typeof(ITypeProvider), typeProvider);
|
||||
|
||||
DesignerSerializationManager manager = new DesignerSerializationManager(serviceContainer);
|
||||
try
|
||||
{
|
||||
using (manager.CreateSession())
|
||||
{
|
||||
WorkflowMarkupSerializationManager xomlSerializationManager = new WorkflowMarkupSerializationManager(manager);
|
||||
root = new WorkflowMarkupSerializer().Deserialize(xomlSerializationManager, workflowDefinitionReader) as Activity;
|
||||
if (root != null && rulesReader != null)
|
||||
{
|
||||
object rules = new WorkflowMarkupSerializer().Deserialize(xomlSerializationManager, rulesReader);
|
||||
root.SetValue(ConditionTypeConverter.DeclarativeConditionDynamicProp, rules);
|
||||
}
|
||||
|
||||
foreach (object error in manager.Errors)
|
||||
{
|
||||
if (error is WorkflowMarkupSerializationException)
|
||||
errors.Add(new ValidationError(((WorkflowMarkupSerializationException)error).Message, ErrorNumbers.Error_SerializationError));
|
||||
else
|
||||
errors.Add(new ValidationError(error.ToString(), ErrorNumbers.Error_SerializationError));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
errors.Add(new ValidationError(e.Message, ErrorNumbers.Error_SerializationError));
|
||||
}
|
||||
|
||||
if (errors.HasErrors)
|
||||
throw new WorkflowValidationFailedException(ExecutionStringManager.WorkflowValidationFailure, errors);
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Workflow.Runtime;
|
||||
using System.Globalization;
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public class DefaultWorkflowSchedulerService : WorkflowSchedulerService
|
||||
{
|
||||
// next two fields controlled by locking the timerQueue
|
||||
private KeyedPriorityQueue<Guid, CallbackInfo, DateTime> timerQueue = new KeyedPriorityQueue<Guid, CallbackInfo, DateTime>();
|
||||
private Timer callbackTimer;
|
||||
|
||||
private TimerCallback timerCallback;
|
||||
private const string MAX_SIMULTANEOUS_WORKFLOWS_KEY = "maxSimultaneousWorkflows";
|
||||
private const int DEFAULT_MAX_SIMULTANEOUS_WORKFLOWS = 5;
|
||||
private static TimeSpan infinite = new TimeSpan(Timeout.Infinite);
|
||||
private readonly int maxSimultaneousWorkflows; // Maximum number of work items allowed in ThreadPool queue
|
||||
private static TimeSpan fiveMinutes = new TimeSpan(0, 5, 0);
|
||||
|
||||
// next three fields controlled by locking the waitingQueue
|
||||
private int numCurrentWorkers;
|
||||
private Queue<WorkItem> waitingQueue; // Queue for extra items waiting to be allowed into thread pool
|
||||
private volatile bool running = false;
|
||||
|
||||
private IList<PerformanceCounter> queueCounters; // expose internal queue length
|
||||
|
||||
private static int DefaultThreadCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return Environment.ProcessorCount == 1
|
||||
? DEFAULT_MAX_SIMULTANEOUS_WORKFLOWS
|
||||
: (int)(DEFAULT_MAX_SIMULTANEOUS_WORKFLOWS * Environment.ProcessorCount * .8);
|
||||
}
|
||||
}
|
||||
|
||||
public DefaultWorkflowSchedulerService()
|
||||
: this(DefaultThreadCount)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public DefaultWorkflowSchedulerService(int maxSimultaneousWorkflows)
|
||||
: base()
|
||||
{
|
||||
if (maxSimultaneousWorkflows < 1)
|
||||
throw new ArgumentOutOfRangeException(MAX_SIMULTANEOUS_WORKFLOWS_KEY, maxSimultaneousWorkflows, String.Empty);
|
||||
this.maxSimultaneousWorkflows = maxSimultaneousWorkflows;
|
||||
init();
|
||||
}
|
||||
|
||||
public DefaultWorkflowSchedulerService(NameValueCollection parameters)
|
||||
: base()
|
||||
{
|
||||
if (parameters == null)
|
||||
throw new ArgumentNullException("parameters");
|
||||
|
||||
maxSimultaneousWorkflows = DefaultThreadCount;
|
||||
foreach (string key in parameters.Keys)
|
||||
{
|
||||
if (key == null)
|
||||
throw new ArgumentException(String.Format(Thread.CurrentThread.CurrentCulture, ExecutionStringManager.UnknownConfigurationParameter, "null"));
|
||||
string p = parameters[key];
|
||||
if (!key.Equals(MAX_SIMULTANEOUS_WORKFLOWS_KEY, StringComparison.OrdinalIgnoreCase))
|
||||
throw new ArgumentException(String.Format(Thread.CurrentThread.CurrentCulture, ExecutionStringManager.UnknownConfigurationParameter, key));
|
||||
if (!int.TryParse(p, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.CurrentCulture, out maxSimultaneousWorkflows))
|
||||
throw new FormatException(MAX_SIMULTANEOUS_WORKFLOWS_KEY);
|
||||
}
|
||||
|
||||
if (maxSimultaneousWorkflows < 1)
|
||||
throw new ArgumentOutOfRangeException(MAX_SIMULTANEOUS_WORKFLOWS_KEY, maxSimultaneousWorkflows, String.Empty);
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
timerCallback = new TimerCallback(OnTimerCallback);
|
||||
timerQueue.FirstElementChanged += OnFirstElementChanged;
|
||||
waitingQueue = new Queue<WorkItem>();
|
||||
}
|
||||
|
||||
|
||||
public int MaxSimultaneousWorkflows
|
||||
{
|
||||
get { return maxSimultaneousWorkflows; }
|
||||
}
|
||||
|
||||
internal protected override void Schedule(WaitCallback callback, Guid workflowInstanceId)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Scheduling work for instance {0}", workflowInstanceId);
|
||||
|
||||
if (callback == null)
|
||||
throw new ArgumentNullException("callback");
|
||||
if (workflowInstanceId == Guid.Empty)
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentUICulture, ExecutionStringManager.CantBeEmptyGuid, "workflowInstanceId"));
|
||||
|
||||
// Add the work item to our internal queue and signal the ProcessQueue thread
|
||||
EnqueueWorkItem(new WorkItem(callback, workflowInstanceId));
|
||||
}
|
||||
|
||||
internal protected override void Schedule(WaitCallback callback, Guid workflowInstanceId, DateTime whenUtc, Guid timerId)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Scheduling work for instance {0} on timer ID {1} in {2}", workflowInstanceId, timerId, (whenUtc - DateTime.UtcNow));
|
||||
|
||||
if (callback == null)
|
||||
throw new ArgumentNullException("callback");
|
||||
if (timerId == Guid.Empty)
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentUICulture, ExecutionStringManager.CantBeEmptyGuid, "timerId"));
|
||||
if (workflowInstanceId == Guid.Empty)
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentUICulture, ExecutionStringManager.CantBeEmptyGuid, "workflowInstanceId"));
|
||||
|
||||
CallbackInfo ci = new CallbackInfo(this, callback, workflowInstanceId, whenUtc);
|
||||
|
||||
lock (timerQueue)
|
||||
{
|
||||
timerQueue.Enqueue(timerId, ci, whenUtc);
|
||||
}
|
||||
}
|
||||
|
||||
internal protected override void Cancel(Guid timerId)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Cancelling work with timer ID {0}", timerId);
|
||||
|
||||
if (timerId == Guid.Empty)
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentUICulture, ExecutionStringManager.CantBeEmptyGuid, "timerId"), "timerId");
|
||||
|
||||
lock (timerQueue)
|
||||
{
|
||||
timerQueue.Remove(timerId);
|
||||
}
|
||||
}
|
||||
|
||||
override protected void OnStarted()
|
||||
{
|
||||
lock (timerQueue)
|
||||
{
|
||||
base.OnStarted();
|
||||
CallbackInfo ci = timerQueue.Peek();
|
||||
if (ci != null)
|
||||
callbackTimer = CreateTimerCallback(ci);
|
||||
running = true;
|
||||
}
|
||||
lock (waitingQueue)
|
||||
{
|
||||
int nToStart = Math.Min(maxSimultaneousWorkflows, waitingQueue.Count);
|
||||
for (int i = 0; i < nToStart; i++)
|
||||
{
|
||||
if (ThreadPool.QueueUserWorkItem(QueueWorkerProcess))
|
||||
{
|
||||
numCurrentWorkers++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (queueCounters == null && this.Runtime.PerformanceCounterManager != null)
|
||||
{
|
||||
queueCounters = this.Runtime.PerformanceCounterManager.CreateCounters(ExecutionStringManager.PerformanceCounterWorkflowsWaitingName);
|
||||
}
|
||||
}
|
||||
|
||||
protected internal override void Stop()
|
||||
{
|
||||
lock (timerQueue)
|
||||
{
|
||||
base.Stop();
|
||||
if (callbackTimer != null)
|
||||
{
|
||||
callbackTimer.Dispose();
|
||||
callbackTimer = null;
|
||||
}
|
||||
running = false;
|
||||
}
|
||||
lock (waitingQueue)
|
||||
{
|
||||
while (numCurrentWorkers > 0)
|
||||
{
|
||||
Monitor.Wait(waitingQueue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFirstElementChanged(object source, KeyedPriorityQueueHeadChangedEventArgs<CallbackInfo> e)
|
||||
{
|
||||
// timerQueue must have been locked by operation that caused this event to fire
|
||||
|
||||
if (callbackTimer != null)
|
||||
{
|
||||
callbackTimer.Dispose();
|
||||
callbackTimer = null;
|
||||
}
|
||||
if (e.NewFirstElement != null && this.State == WorkflowRuntimeServiceState.Started)
|
||||
{
|
||||
callbackTimer = CreateTimerCallback(e.NewFirstElement);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTimerCallback(object ignored)
|
||||
{
|
||||
//Make sure activity ID comes out of Threadpool are initialized to null.
|
||||
Trace.CorrelationManager.ActivityId = Guid.Empty;
|
||||
|
||||
CallbackInfo ci = null;
|
||||
bool fire = false;
|
||||
try
|
||||
{
|
||||
lock (timerQueue)
|
||||
{
|
||||
if (State == WorkflowRuntimeServiceState.Started)
|
||||
{
|
||||
ci = timerQueue.Peek();
|
||||
if (ci != null)
|
||||
{
|
||||
if (ci.IsExpired)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Timeout occured for timer for instance {0}", ci.State);
|
||||
timerQueue.Dequeue();
|
||||
fire = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
callbackTimer = CreateTimerCallback(ci);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fire && ci != null)
|
||||
ci.Callback(ci.State);
|
||||
}
|
||||
// Ignore cases where the workflow has been stolen out from under us
|
||||
catch (WorkflowOwnershipException)
|
||||
{ }
|
||||
catch (ThreadAbortException e)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Error, 0, "Timeout for instance, {0} threw exception {1}", ci == null ? null : ci.State, e.Message);
|
||||
RaiseServicesExceptionNotHandledEvent(e, (Guid)ci.State);
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Error, 0, "Timeout for instance, {0} threw exception {1}", ci == null ? null : ci.State, e.Message);
|
||||
RaiseServicesExceptionNotHandledEvent(e, (Guid)ci.State);
|
||||
}
|
||||
}
|
||||
|
||||
private Timer CreateTimerCallback(CallbackInfo info)
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
TimeSpan span = (info.When > now) ? info.When - now : TimeSpan.Zero;
|
||||
if (span > fiveMinutes) // never let more than five minutes go by without checking
|
||||
span = fiveMinutes;
|
||||
return new Timer(timerCallback, info.State, span, infinite);
|
||||
}
|
||||
|
||||
private void EnqueueWorkItem(WorkItem workItem)
|
||||
{
|
||||
lock (waitingQueue)
|
||||
{
|
||||
waitingQueue.Enqueue(workItem);
|
||||
if (running && numCurrentWorkers < maxSimultaneousWorkflows)
|
||||
{
|
||||
if (ThreadPool.QueueUserWorkItem(this.QueueWorkerProcess))
|
||||
{
|
||||
numCurrentWorkers++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (queueCounters != null)
|
||||
{
|
||||
foreach (PerformanceCounter p in queueCounters)
|
||||
{
|
||||
p.RawValue = waitingQueue.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueWorkerProcess(object state /*unused*/)
|
||||
{
|
||||
//Make sure activity ID comes out of Threadpool are initialized to null.
|
||||
Trace.CorrelationManager.ActivityId = Guid.Empty;
|
||||
|
||||
while (true)
|
||||
{
|
||||
WorkItem workItem;
|
||||
lock (waitingQueue)
|
||||
{
|
||||
if (waitingQueue.Count == 0 || !running)
|
||||
{
|
||||
numCurrentWorkers--;
|
||||
Monitor.Pulse(waitingQueue);
|
||||
return;
|
||||
}
|
||||
workItem = waitingQueue.Dequeue();
|
||||
}
|
||||
if (queueCounters != null)
|
||||
{
|
||||
foreach (PerformanceCounter p in queueCounters)
|
||||
{
|
||||
p.RawValue = waitingQueue.Count;
|
||||
}
|
||||
}
|
||||
workItem.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class WorkItem
|
||||
{
|
||||
private WaitCallback callback;
|
||||
private object state;
|
||||
|
||||
public WorkItem(WaitCallback callback, object state)
|
||||
{
|
||||
this.callback = callback;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public WaitCallback Callback
|
||||
{
|
||||
get { return callback; }
|
||||
}
|
||||
|
||||
public void Invoke(WorkflowSchedulerService service)
|
||||
{
|
||||
try
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Running workflow {0}", state);
|
||||
Callback(state);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (WorkflowExecutor.IsIrrecoverableException(e))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
else
|
||||
{
|
||||
service.RaiseExceptionNotHandledEvent(e, (Guid)state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class CallbackInfo
|
||||
{
|
||||
WaitCallback callback;
|
||||
object state;
|
||||
DateTime when;
|
||||
WorkflowSchedulerService service;
|
||||
|
||||
public CallbackInfo(WorkflowSchedulerService service, WaitCallback callback, object state, DateTime when)
|
||||
{
|
||||
this.service = service;
|
||||
this.callback = callback;
|
||||
this.state = state;
|
||||
this.when = when;
|
||||
}
|
||||
|
||||
public DateTime When
|
||||
{
|
||||
get { return when; }
|
||||
}
|
||||
|
||||
public bool IsExpired
|
||||
{
|
||||
get { return DateTime.UtcNow >= when; }
|
||||
}
|
||||
|
||||
public object State
|
||||
{
|
||||
get { return state; }
|
||||
}
|
||||
|
||||
public WaitCallback Callback
|
||||
{
|
||||
get { return callback; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="DefaultWorkflowTransactionService.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Transactions;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Configuration;
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
/// <summary> A simple TransactionService that creates
|
||||
/// <c>System.Transactions.CommittableTransaction</c>.</summary>
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public class DefaultWorkflowCommitWorkBatchService : WorkflowCommitWorkBatchService
|
||||
{
|
||||
private bool _enableRetries = false;
|
||||
private bool _ignoreCommonEnableRetries = false;
|
||||
|
||||
public DefaultWorkflowCommitWorkBatchService()
|
||||
{
|
||||
}
|
||||
|
||||
public DefaultWorkflowCommitWorkBatchService(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnableRetries
|
||||
{
|
||||
get { return _enableRetries; }
|
||||
set
|
||||
{
|
||||
_enableRetries = value;
|
||||
_ignoreCommonEnableRetries = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected internal override void Start()
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "DefaultWorkflowCommitWorkBatchService: Starting");
|
||||
|
||||
//
|
||||
// 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, "DefaultWorkflowCommitWorkBatchService: Stopping");
|
||||
|
||||
base.OnStopped();
|
||||
}
|
||||
|
||||
internal protected override void CommitWorkBatch(CommitWorkBatchCallback commitWorkBatchCallback)
|
||||
{
|
||||
DbRetry dbRetry = new DbRetry(_enableRetries);
|
||||
short retryCounter = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (null != Transaction.Current)
|
||||
{
|
||||
//
|
||||
// Can't retry as we don't own the tx
|
||||
// Set the counter to only allow one iteration
|
||||
retryCounter = dbRetry.MaxRetries;
|
||||
}
|
||||
try
|
||||
{
|
||||
base.CommitWorkBatch(commitWorkBatchCallback);
|
||||
|
||||
break;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Error, 0, "DefaultWorkflowCommitWorkBatchService caught exception from commitWorkBatchCallback: " + e.ToString());
|
||||
|
||||
if (dbRetry.TryDoRetry(ref retryCounter))
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "DefaultWorkflowCommitWorkBatchService retrying commitWorkBatchCallback (retry attempt " + retryCounter.ToString(System.Globalization.CultureInfo.InvariantCulture) + ")");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="LocalTransaction.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Transactions;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Threading;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// This class wraps a local transaction (DbTransaction) by implementing IPromotableSinglePhaseNotification:
|
||||
/// It instantiate a DbTransaction and never allows promotion to a DTC transaction.
|
||||
/// </summary>
|
||||
internal sealed class LocalTransaction : IPromotableSinglePhaseNotification
|
||||
{
|
||||
readonly DbTransaction transaction;
|
||||
System.Data.Common.DbConnection connection;
|
||||
ManualResetEvent handle;
|
||||
object syncRoot = new Object();
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Wraps a local transaction inside
|
||||
/// </summary>
|
||||
/// <param name="dbHelper"></param>
|
||||
internal LocalTransaction(DbResourceAllocator dbHelper, ManualResetEvent handle)
|
||||
{
|
||||
if (null == handle)
|
||||
throw new ArgumentNullException("handle");
|
||||
|
||||
// Open a connection that specifically does not auto-enlist ("Enlist=false" in connection string)
|
||||
// to prevent auto-promotion of the transaction
|
||||
this.connection = dbHelper.OpenNewConnectionNoEnlist();
|
||||
this.transaction = this.connection.BeginTransaction();
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
|
||||
#region Accessors
|
||||
|
||||
public System.Data.Common.DbConnection Connection
|
||||
{
|
||||
get { return this.connection; }
|
||||
}
|
||||
|
||||
public DbTransaction Transaction
|
||||
{
|
||||
get { return this.transaction; }
|
||||
}
|
||||
|
||||
#endregion Accessors
|
||||
|
||||
|
||||
#region IPromotableSinglePhaseNotification Members
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
}
|
||||
|
||||
public void SinglePhaseCommit(SinglePhaseEnlistment en)
|
||||
{
|
||||
if (en == null)
|
||||
throw new ArgumentNullException("en");
|
||||
|
||||
//
|
||||
// Wait until IPendingWork members have completed (WinOE bugs 17580 and 13395)
|
||||
try
|
||||
{
|
||||
handle.WaitOne();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// If an ObjectDisposedException is thrown because
|
||||
// the WaitHandle has already closed, nothing to worry
|
||||
// about. Move on.
|
||||
}
|
||||
|
||||
lock (syncRoot)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.transaction.Commit();
|
||||
en.Committed();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
en.Aborted(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ((null != connection) && (ConnectionState.Closed != connection.State))
|
||||
{
|
||||
connection.Close();
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Rollback(SinglePhaseEnlistment en)
|
||||
{
|
||||
if (en == null)
|
||||
throw new ArgumentNullException("en");
|
||||
|
||||
//
|
||||
// Wait until IPendingWork members have completed (WinOE bugs 17580 and 13395)
|
||||
try
|
||||
{
|
||||
handle.WaitOne();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// If an ObjectDisposedException is thrown because
|
||||
// the WaitHandle has already closed, nothing to worry
|
||||
// about. Move on.
|
||||
}
|
||||
// DbTransaction.Dispose will call Rollback if the DbTransaction is not Zombied.
|
||||
// Not safe to call DbTransaction.Rollback here as the the underlining
|
||||
// DbTransaction may have already been Rolled back
|
||||
lock (syncRoot)
|
||||
{
|
||||
if (null != transaction)
|
||||
transaction.Dispose();
|
||||
|
||||
if ((null != connection) && (ConnectionState.Closed != connection.State))
|
||||
{
|
||||
connection.Close();
|
||||
connection = null;
|
||||
}
|
||||
|
||||
en.Aborted();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Promote()
|
||||
{
|
||||
throw new TransactionPromotionException(
|
||||
ExecutionStringManager.PromotionNotSupported);
|
||||
}
|
||||
|
||||
#endregion IPromotableSinglePhaseNotification Members
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Workflow.Runtime;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public class ManualWorkflowSchedulerService : WorkflowSchedulerService
|
||||
{
|
||||
private class CallbackInfo
|
||||
{
|
||||
WaitCallback callback;
|
||||
Guid instanceId;
|
||||
Guid timerId;
|
||||
DateTime when;
|
||||
|
||||
public CallbackInfo(WaitCallback callback, Guid instanceId, Guid timerId, DateTime when)
|
||||
{
|
||||
this.callback = callback;
|
||||
this.when = when;
|
||||
this.instanceId = instanceId;
|
||||
this.timerId = timerId;
|
||||
}
|
||||
|
||||
public DateTime When
|
||||
{
|
||||
get { return when; }
|
||||
}
|
||||
|
||||
public bool IsExpired
|
||||
{
|
||||
get { return DateTime.UtcNow >= when; }
|
||||
}
|
||||
|
||||
public Guid InstanceId { get { return instanceId; } }
|
||||
|
||||
public Guid TimerId { get { return timerId; } }
|
||||
|
||||
public WaitCallback Callback
|
||||
{
|
||||
get { return callback; }
|
||||
}
|
||||
}
|
||||
|
||||
private KeyedPriorityQueue<Guid, CallbackInfo, DateTime> pendingScheduleRequests = new KeyedPriorityQueue<Guid, CallbackInfo, DateTime>();
|
||||
private Dictionary<Guid, DefaultWorkflowSchedulerService.WorkItem> scheduleRequests = new Dictionary<Guid, DefaultWorkflowSchedulerService.WorkItem>();
|
||||
private object locker = new Object();
|
||||
private Timer callbackTimer;
|
||||
private readonly TimerCallback timerCallback; // non-null indicates that active timers are enabled
|
||||
private volatile bool threadRunning; // indicates that the timer thread is running
|
||||
private static TimeSpan infinite = new TimeSpan(Timeout.Infinite);
|
||||
private IList<PerformanceCounter> queueCounters;
|
||||
private static TimeSpan fiveMinutes = new TimeSpan(0, 5, 0);
|
||||
|
||||
private const string USE_ACTIVE_TIMERS_KEY = "UseActiveTimers";
|
||||
|
||||
// Note that pendingScheduleRequests are keyed by instance ID under the assertion that there is at most one outstanding
|
||||
// timer for any given instance ID. To support cancellation, and additional map is kept of timerID-to-instanceID so that
|
||||
// we can find the appropriate pending given a timer ID
|
||||
|
||||
public ManualWorkflowSchedulerService()
|
||||
{
|
||||
}
|
||||
|
||||
public ManualWorkflowSchedulerService(bool useActiveTimers)
|
||||
{
|
||||
if (useActiveTimers)
|
||||
{
|
||||
timerCallback = new TimerCallback(OnTimerCallback);
|
||||
pendingScheduleRequests.FirstElementChanged += OnFirstElementChanged;
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "ManualWorkflowSchedulerService: started with active timers");
|
||||
}
|
||||
}
|
||||
public ManualWorkflowSchedulerService(NameValueCollection parameters)
|
||||
{
|
||||
if (parameters == null)
|
||||
throw new ArgumentNullException("parameters");
|
||||
|
||||
foreach (string key in parameters.Keys)
|
||||
{
|
||||
if (key == null)
|
||||
throw new ArgumentException(String.Format(Thread.CurrentThread.CurrentCulture, ExecutionStringManager.UnknownConfigurationParameter, "null"));
|
||||
string p = parameters[key];
|
||||
if (!key.Equals(USE_ACTIVE_TIMERS_KEY, StringComparison.OrdinalIgnoreCase))
|
||||
throw new ArgumentException(String.Format(Thread.CurrentThread.CurrentCulture, ExecutionStringManager.UnknownConfigurationParameter, key));
|
||||
bool useActiveTimers;
|
||||
if (!bool.TryParse(p, out useActiveTimers))
|
||||
throw new FormatException(USE_ACTIVE_TIMERS_KEY);
|
||||
if (useActiveTimers)
|
||||
{
|
||||
timerCallback = new TimerCallback(OnTimerCallback);
|
||||
pendingScheduleRequests.FirstElementChanged += OnFirstElementChanged;
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "ManualWorkflowSchedulerService: Started with active timers");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal protected override void Schedule(WaitCallback callback, Guid workflowInstanceId)
|
||||
{
|
||||
if (callback == null)
|
||||
throw new ArgumentNullException("callback");
|
||||
if (workflowInstanceId.Equals(Guid.Empty))
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.CantBeEmptyGuid, "workflowInstanceId"));
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "ManualWorkflowSchedulerService: Schedule workflow {0}", workflowInstanceId);
|
||||
if (!scheduleRequests.ContainsKey(workflowInstanceId))
|
||||
scheduleRequests.Add(workflowInstanceId, new DefaultWorkflowSchedulerService.WorkItem(callback, workflowInstanceId));
|
||||
}
|
||||
if (queueCounters != null)
|
||||
{
|
||||
foreach (PerformanceCounter p in queueCounters)
|
||||
{
|
||||
p.RawValue = scheduleRequests.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal protected override void Schedule(WaitCallback callback, Guid workflowInstanceId, DateTime whenUtc, Guid timerId)
|
||||
{
|
||||
if (callback == null)
|
||||
throw new ArgumentNullException("callback");
|
||||
if (workflowInstanceId.Equals(Guid.Empty))
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.CantBeEmptyGuid, "workflowInstanceId"));
|
||||
if (timerId.Equals(Guid.Empty))
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.CantBeEmptyGuid, "timerId"));
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "ManualWorkflowSchedulerService: Schedule timer {0} for workflow {1} at {2}", timerId, workflowInstanceId, whenUtc);
|
||||
pendingScheduleRequests.Enqueue(timerId, new CallbackInfo(callback, workflowInstanceId, timerId, whenUtc), whenUtc);
|
||||
}
|
||||
}
|
||||
|
||||
internal protected override void Cancel(Guid timerId)
|
||||
{
|
||||
if (timerId.Equals(Guid.Empty))
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.CantBeEmptyGuid, "timerId"));
|
||||
|
||||
lock (locker)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "ManualWorkflowSchedulerService: Cancel timer {0}", timerId);
|
||||
pendingScheduleRequests.Remove(timerId);
|
||||
}
|
||||
}
|
||||
|
||||
private bool RunOne(Guid workflowInstanceId)
|
||||
{
|
||||
bool retval = false;
|
||||
DefaultWorkflowSchedulerService.WorkItem cs = null;
|
||||
lock (locker)
|
||||
{
|
||||
if (scheduleRequests.ContainsKey(workflowInstanceId))
|
||||
{
|
||||
cs = scheduleRequests[workflowInstanceId];
|
||||
scheduleRequests.Remove(workflowInstanceId);
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
if (cs != null)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "ManualWorkflowSchedulerService: Executing {0}", workflowInstanceId);
|
||||
if (queueCounters != null)
|
||||
{
|
||||
foreach (PerformanceCounter p in queueCounters)
|
||||
{
|
||||
p.RawValue = scheduleRequests.Count;
|
||||
}
|
||||
}
|
||||
cs.Invoke(this);
|
||||
retval = true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
RaiseServicesExceptionNotHandledEvent(e, workflowInstanceId);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
private bool HasExpiredTimer(Guid workflowInstanceId, out Guid timerId)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
CallbackInfo ci = pendingScheduleRequests.FindByPriority(DateTime.UtcNow,
|
||||
delegate(CallbackInfo c) { return c.InstanceId == workflowInstanceId; });
|
||||
if (ci != null)
|
||||
{
|
||||
timerId = ci.TimerId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
timerId = Guid.Empty;
|
||||
return false;
|
||||
}
|
||||
private bool ProcessTimer(Guid workflowInstanceId)
|
||||
{
|
||||
bool retval = false;
|
||||
CallbackInfo cs = null;
|
||||
Guid timerId = Guid.Empty;
|
||||
lock (locker)
|
||||
{
|
||||
Guid expTimerId;
|
||||
if (HasExpiredTimer(workflowInstanceId, out expTimerId))
|
||||
{
|
||||
cs = pendingScheduleRequests.Remove(expTimerId);
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
if (cs != null)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "ManualWorkflowSchedulerService: Processing timer {0}", timerId);
|
||||
cs.Callback(cs.InstanceId);
|
||||
retval = true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
RaiseServicesExceptionNotHandledEvent(e, workflowInstanceId);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
private bool CanRun(Guid workflowInstanceId)
|
||||
{
|
||||
bool retval = false;
|
||||
lock (locker)
|
||||
{
|
||||
Guid timerId;
|
||||
retval = scheduleRequests.ContainsKey(workflowInstanceId) || HasExpiredTimer(workflowInstanceId, out timerId);
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "ManualWorkflowSchedulerService: CanRun is {0}", retval);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
public bool RunWorkflow(Guid workflowInstanceId)
|
||||
{
|
||||
if (workflowInstanceId.Equals(Guid.Empty))
|
||||
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.CantBeEmptyGuid, "workflowInstanceId"));
|
||||
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "ManualWorkflowSchedulerService: Running workflow {0}", workflowInstanceId);
|
||||
|
||||
bool retval = false; // return true if we do any work at all
|
||||
while (CanRun(workflowInstanceId))
|
||||
{
|
||||
if (RunOne(workflowInstanceId) || ProcessTimer(workflowInstanceId))
|
||||
retval = true; // did some work, try again
|
||||
else
|
||||
break; // no work done this iteration
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
private Timer CreateTimerCallback(CallbackInfo info)
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
TimeSpan span = (info.When > now) ? info.When - now : TimeSpan.Zero;
|
||||
// never let more than five minutes go by without checking
|
||||
if (span > fiveMinutes)
|
||||
{
|
||||
span = fiveMinutes;
|
||||
}
|
||||
return new Timer(timerCallback, info.InstanceId, span, infinite);
|
||||
}
|
||||
override protected void OnStarted()
|
||||
{
|
||||
base.OnStarted();
|
||||
if (this.timerCallback != null)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
CallbackInfo ci = pendingScheduleRequests.Peek();
|
||||
if (ci != null)
|
||||
callbackTimer = CreateTimerCallback(ci);
|
||||
}
|
||||
}
|
||||
lock (locker)
|
||||
{
|
||||
if (queueCounters == null && this.Runtime.PerformanceCounterManager != null)
|
||||
{
|
||||
queueCounters = this.Runtime.PerformanceCounterManager.CreateCounters(ExecutionStringManager.PerformanceCounterWorkflowsWaitingName);
|
||||
}
|
||||
}
|
||||
}
|
||||
protected internal override void Stop()
|
||||
{
|
||||
base.Stop();
|
||||
if (this.timerCallback != null)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
if (callbackTimer != null)
|
||||
{
|
||||
callbackTimer.Dispose();
|
||||
callbackTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void OnTimerCallback(object ignored)
|
||||
{
|
||||
CallbackInfo ci = null;
|
||||
try
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
if (State.Equals(WorkflowRuntimeServiceState.Started))
|
||||
{
|
||||
ci = pendingScheduleRequests.Peek();
|
||||
if (ci != null)
|
||||
{
|
||||
if (ci.IsExpired)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Timeout occured for timer for instance {0}", ci.InstanceId);
|
||||
threadRunning = true;
|
||||
pendingScheduleRequests.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
callbackTimer = CreateTimerCallback(ci);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (threadRunning)
|
||||
{
|
||||
ci.Callback(ci.InstanceId); // delivers the timer message
|
||||
RunWorkflow(ci.InstanceId);
|
||||
}
|
||||
}
|
||||
catch (ThreadAbortException e)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Error, 0, "Timeout for instance, {0} threw exception {1}", ci == null ? Guid.Empty : ci.InstanceId, e.Message);
|
||||
RaiseServicesExceptionNotHandledEvent(e, ci.InstanceId);
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Error, 0, "Timeout for instance, {0} threw exception {1}", ci == null ? Guid.Empty : ci.InstanceId, e.Message);
|
||||
RaiseServicesExceptionNotHandledEvent(e, ci.InstanceId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
if (threadRunning)
|
||||
{
|
||||
threadRunning = false;
|
||||
ci = pendingScheduleRequests.Peek();
|
||||
if (ci != null)
|
||||
callbackTimer = CreateTimerCallback(ci);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void OnFirstElementChanged(object source, KeyedPriorityQueueHeadChangedEventArgs<CallbackInfo> e)
|
||||
{
|
||||
lock (locker)
|
||||
{
|
||||
if (threadRunning)
|
||||
return; // ignore when a timer thread is already processing a timer request
|
||||
if (callbackTimer != null)
|
||||
{
|
||||
callbackTimer.Dispose();
|
||||
callbackTimer = null;
|
||||
}
|
||||
if (e.NewFirstElement != null && this.State == WorkflowRuntimeServiceState.Started)
|
||||
{
|
||||
callbackTimer = CreateTimerCallback(e.NewFirstElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Security.Permissions;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Workflow;
|
||||
using System.Workflow.Runtime;
|
||||
using System.Workflow.ComponentModel;
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
#region Runtime Exceptions
|
||||
[Serializable]
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public class PersistenceException : SystemException
|
||||
{
|
||||
public PersistenceException() : base(string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.PersistenceException)) { }
|
||||
public PersistenceException(string message) : base(message) { }
|
||||
public PersistenceException(string message, Exception innerException) : base(message, innerException) { }
|
||||
|
||||
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
|
||||
protected PersistenceException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="SharedConnectionInfo.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Transactions;
|
||||
using System.Data.Common;
|
||||
using System.Threading;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// This class keeps the following associated with a Transaction
|
||||
/// - a connection that participates in the transaction.
|
||||
/// - an optional local transaction (DbTransaction) generated from the single-phase-committed Transaction.
|
||||
/// The connection and the local transaction are passed around to different host components to
|
||||
/// do transacted DB work using the shared connection.
|
||||
/// </summary>
|
||||
internal sealed class SharedConnectionInfo : IDisposable
|
||||
{
|
||||
readonly DbConnection connection;
|
||||
readonly DbTransaction localTransaction;
|
||||
private bool disposed;
|
||||
private ManualResetEvent handle;
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Instantiate an opened connection enlisted to the Transaction
|
||||
/// if promotable is false, the Transaction wraps a local
|
||||
/// transaction inside and can never be promoted
|
||||
/// </summary>
|
||||
/// <param name="dbResourceAllocator"></param>
|
||||
/// <param name="transaction"></param>
|
||||
/// <param name="wantPromotable"></param>
|
||||
internal SharedConnectionInfo(
|
||||
DbResourceAllocator dbResourceAllocator,
|
||||
Transaction transaction,
|
||||
bool wantPromotable,
|
||||
ManualResetEvent handle)
|
||||
{
|
||||
Debug.Assert((transaction != null), "Null Transaction!");
|
||||
|
||||
if (null == handle)
|
||||
throw new ArgumentNullException("handle");
|
||||
|
||||
this.handle = handle;
|
||||
|
||||
if (wantPromotable)
|
||||
{
|
||||
// Enlist a newly opened connection to this regular Transaction
|
||||
this.connection = dbResourceAllocator.OpenNewConnection();
|
||||
this.connection.EnlistTransaction(transaction);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make this transaction no longer promotable by attaching our
|
||||
// IPromotableSinglePhaseNotification implementation (LocalTranscaction)
|
||||
// and track the DbConnection and DbTransaction associated with the LocalTranscaction
|
||||
LocalTransaction localTransaction = new LocalTransaction(dbResourceAllocator, handle);
|
||||
transaction.EnlistPromotableSinglePhase(localTransaction);
|
||||
this.connection = localTransaction.Connection;
|
||||
this.localTransaction = localTransaction.Transaction;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Accessors
|
||||
|
||||
internal DbConnection DBConnection
|
||||
{
|
||||
get { return this.connection; }
|
||||
}
|
||||
|
||||
internal DbTransaction DBTransaction
|
||||
{
|
||||
get { return this.localTransaction; }
|
||||
}
|
||||
|
||||
#endregion Accessors
|
||||
|
||||
#region IDisposable Members
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!this.disposed)
|
||||
{
|
||||
//
|
||||
// If we're using a LocalTransaction it will close the connection
|
||||
// in it's IPromotableSinglePhaseNotification methods
|
||||
if ((this.localTransaction == null) && (null != connection))
|
||||
this.connection.Dispose();
|
||||
}
|
||||
this.disposed = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
#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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Transactions;
|
||||
using System.Diagnostics;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlTypes;
|
||||
using System.Data.SqlClient;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Configuration;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Security.Permissions;
|
||||
using System.Threading;
|
||||
|
||||
using System.Workflow.Runtime.Hosting;
|
||||
using System.Workflow.Runtime;
|
||||
using System.Workflow.ComponentModel;
|
||||
using System.Globalization;
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public class SqlPersistenceWorkflowInstanceDescription
|
||||
{
|
||||
private Guid workflowInstanceId;
|
||||
private WorkflowStatus status;
|
||||
private bool isBlocked;
|
||||
private string suspendOrTerminateDescription;
|
||||
private SqlDateTime nextTimerExpiration;
|
||||
|
||||
internal SqlPersistenceWorkflowInstanceDescription(Guid workflowInstanceId, WorkflowStatus status, bool isBlocked, string suspendOrTerminateDescription, SqlDateTime nextTimerExpiration)
|
||||
{
|
||||
this.workflowInstanceId = workflowInstanceId;
|
||||
this.status = status;
|
||||
this.isBlocked = isBlocked;
|
||||
this.suspendOrTerminateDescription = suspendOrTerminateDescription;
|
||||
this.nextTimerExpiration = nextTimerExpiration;
|
||||
}
|
||||
|
||||
public Guid WorkflowInstanceId { get { return workflowInstanceId; } }
|
||||
public WorkflowStatus Status { get { return status; } }
|
||||
public bool IsBlocked { get { return isBlocked; } }
|
||||
public string SuspendOrTerminateDescription { get { return suspendOrTerminateDescription; } }
|
||||
public SqlDateTime NextTimerExpiration { get { return nextTimerExpiration; } }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
#region Imports
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using System.ComponentModel.Design;
|
||||
using System.ComponentModel.Design.Serialization;
|
||||
using System.Workflow.ComponentModel;
|
||||
using System.Workflow.ComponentModel.Compiler;
|
||||
using System.Workflow.ComponentModel.Serialization;
|
||||
using System.Workflow.ComponentModel.Design;
|
||||
using System.Workflow.Runtime;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public abstract class WorkflowLoaderService : WorkflowRuntimeService
|
||||
{
|
||||
protected internal abstract Activity CreateInstance(Type workflowType);
|
||||
protected internal abstract Activity CreateInstance(XmlReader workflowDefinitionReader, XmlReader rulesReader);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="StatePersistenceService.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Workflow.Runtime;
|
||||
using System.Workflow.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
/// <summary> Service for saving engine state. </summary>
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public abstract class WorkflowPersistenceService : WorkflowRuntimeService
|
||||
{
|
||||
/// <summary> Saves the state of a workflow instance. </summary>
|
||||
/// <param name="state"> The workflow instance state to save </param>
|
||||
internal protected abstract void SaveWorkflowInstanceState(Activity rootActivity, bool unlock);
|
||||
|
||||
/// <summary></summary>
|
||||
/// <param name="state"></param>
|
||||
internal protected abstract void UnlockWorkflowInstanceState(Activity rootActivity);
|
||||
|
||||
/// <summary> Loads the state of a workflow instance. </summary>
|
||||
/// <param name="instanceId"> The unique ID of the instance to load </param>
|
||||
/// <returns> The workflow instance state</returns>
|
||||
internal protected abstract Activity LoadWorkflowInstanceState(Guid instanceId);
|
||||
|
||||
/// <summary> Saves the state of a completed scope. </summary>
|
||||
/// <param name="completedScopeState"> The completed scope to save </param>
|
||||
internal protected abstract void SaveCompletedContextActivity(Activity activity);
|
||||
|
||||
/// <summary> Loads the state of a completed scope </summary>
|
||||
/// <param name="scopeId"> The unique identifier of the completed scope </param>
|
||||
/// <returns> The completed scope or null </returns>
|
||||
internal protected abstract Activity LoadCompletedContextActivity(Guid scopeId, Activity outerActivity);
|
||||
|
||||
/// <summary></summary>
|
||||
/// <param name="activity"></param>
|
||||
/// <returns>The value of the "UnloadOnIdle" flag</returns>
|
||||
internal protected abstract bool UnloadOnIdle(Activity activity);
|
||||
|
||||
static protected byte[] GetDefaultSerializedForm(Activity activity)
|
||||
{
|
||||
DateTime startTime = DateTime.Now;
|
||||
Byte[] result;
|
||||
|
||||
Debug.Assert(activity != null, "Null activity");
|
||||
using (MemoryStream stream = new MemoryStream(10240))
|
||||
{
|
||||
stream.Position = 0;
|
||||
activity.Save(stream);
|
||||
using (MemoryStream compressedStream = new MemoryStream((int)stream.Length))
|
||||
{
|
||||
using (GZipStream gzs = new GZipStream(compressedStream, CompressionMode.Compress, true))
|
||||
{
|
||||
gzs.Write(stream.GetBuffer(), 0, (int)stream.Length);
|
||||
}
|
||||
|
||||
ActivityExecutionContextInfo executionContextInfo = (ActivityExecutionContextInfo)activity.GetValue(Activity.ActivityExecutionContextInfoProperty);
|
||||
TimeSpan timeElapsed = DateTime.Now - startTime;
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0,
|
||||
"Serialized a {0} with id {1} to length {2}. Took {3}.",
|
||||
executionContextInfo, executionContextInfo.ContextGuid, compressedStream.Length, timeElapsed);
|
||||
|
||||
result = compressedStream.GetBuffer();
|
||||
Array.Resize<Byte>(ref result, Convert.ToInt32(compressedStream.Length));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static protected Activity RestoreFromDefaultSerializedForm(Byte[] activityBytes, Activity outerActivity)
|
||||
{
|
||||
DateTime startTime = DateTime.Now;
|
||||
Activity state;
|
||||
|
||||
MemoryStream stream = new MemoryStream(activityBytes);
|
||||
stream.Position = 0;
|
||||
|
||||
using (GZipStream gzs = new GZipStream(stream, CompressionMode.Decompress, true))
|
||||
{
|
||||
state = Activity.Load(gzs, outerActivity);
|
||||
}
|
||||
Debug.Assert(state != null, "invalid state recovered");
|
||||
TimeSpan timeElapsed = DateTime.Now - startTime;
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0,
|
||||
"Deserialized a {0} to length {1}. Took {2}.",
|
||||
state, stream.Length, timeElapsed);
|
||||
|
||||
return state;
|
||||
}
|
||||
static protected internal bool GetIsBlocked(Activity rootActivity)
|
||||
{
|
||||
return (bool)rootActivity.GetValue(WorkflowExecutor.IsBlockedProperty);
|
||||
}
|
||||
static protected internal string GetSuspendOrTerminateInfo(Activity rootActivity)
|
||||
{
|
||||
return (string)rootActivity.GetValue(WorkflowExecutor.SuspendOrTerminateInfoProperty);
|
||||
}
|
||||
static protected internal WorkflowStatus GetWorkflowStatus(Activity rootActivity)
|
||||
{
|
||||
return (WorkflowStatus)rootActivity.GetValue(WorkflowExecutor.WorkflowStatusProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// <copyright file="WorkflowRuntimeService.cs" company="Microsoft">Copyright (c) Microsoft Corporation. All rights reserved.</copyright>
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
using System.Workflow.Runtime;
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public enum WorkflowRuntimeServiceState
|
||||
{
|
||||
Stopped,
|
||||
Starting,
|
||||
Started,
|
||||
Stopping
|
||||
}
|
||||
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
abstract public class WorkflowRuntimeService
|
||||
{
|
||||
private WorkflowRuntime _runtime;
|
||||
private WorkflowRuntimeServiceState state = WorkflowRuntimeServiceState.Stopped;
|
||||
|
||||
protected WorkflowRuntime Runtime
|
||||
{
|
||||
get
|
||||
{
|
||||
return _runtime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal void SetRuntime(WorkflowRuntime runtime)
|
||||
{
|
||||
if (runtime == null && _runtime != null)
|
||||
{
|
||||
_runtime.Started -= this.HandleStarted;
|
||||
_runtime.Stopped -= this.HandleStopped;
|
||||
}
|
||||
_runtime = runtime;
|
||||
if (runtime != null)
|
||||
{
|
||||
_runtime.Started += this.HandleStarted;
|
||||
_runtime.Stopped += this.HandleStopped;
|
||||
}
|
||||
}
|
||||
|
||||
protected void RaiseServicesExceptionNotHandledEvent(Exception exception, Guid instanceId)
|
||||
{
|
||||
Runtime.RaiseServicesExceptionNotHandledEvent(exception, instanceId);
|
||||
}
|
||||
|
||||
internal void RaiseExceptionNotHandledEvent(Exception exception, Guid instanceId)
|
||||
{
|
||||
Runtime.RaiseServicesExceptionNotHandledEvent(exception, instanceId);
|
||||
}
|
||||
|
||||
protected WorkflowRuntimeServiceState State
|
||||
{
|
||||
get { return state; }
|
||||
}
|
||||
|
||||
virtual internal protected void Start()
|
||||
{
|
||||
if (_runtime == null)
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.ServiceNotAddedToRuntime, this.GetType().Name));
|
||||
if (state.Equals(WorkflowRuntimeServiceState.Started))
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.ServiceAlreadyStarted, this.GetType().Name));
|
||||
|
||||
state = WorkflowRuntimeServiceState.Starting;
|
||||
}
|
||||
|
||||
virtual internal protected void Stop()
|
||||
{
|
||||
if (_runtime == null)
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.ServiceNotAddedToRuntime, this.GetType().Name));
|
||||
if (state.Equals(WorkflowRuntimeServiceState.Stopped))
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, ExecutionStringManager.ServiceNotStarted, this.GetType().Name));
|
||||
|
||||
state = WorkflowRuntimeServiceState.Stopping;
|
||||
}
|
||||
|
||||
virtual protected void OnStarted()
|
||||
{ }
|
||||
|
||||
virtual protected void OnStopped()
|
||||
{ }
|
||||
|
||||
private void HandleStarted(object source, WorkflowRuntimeEventArgs e)
|
||||
{
|
||||
state = WorkflowRuntimeServiceState.Started;
|
||||
this.OnStarted();
|
||||
}
|
||||
|
||||
private void HandleStopped(object source, WorkflowRuntimeEventArgs e)
|
||||
{
|
||||
state = WorkflowRuntimeServiceState.Stopped;
|
||||
this.OnStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public abstract class WorkflowSchedulerService : WorkflowRuntimeService
|
||||
{
|
||||
internal protected abstract void Schedule(WaitCallback callback, Guid workflowInstanceId);
|
||||
internal protected abstract void Schedule(WaitCallback callback, Guid workflowInstanceId, DateTime whenUtc, Guid timerId);
|
||||
internal protected abstract void Cancel(Guid timerId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//------------------------------------------------------------
|
||||
using System.Workflow.ComponentModel;
|
||||
using System.Workflow.Runtime;
|
||||
using System.Threading;
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
class WorkflowTimerService : WorkflowRuntimeService, ITimerService
|
||||
{
|
||||
public WorkflowTimerService()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
public void ScheduleTimer(WaitCallback callback, Guid workflowInstanceId, DateTime whenUtc, Guid timerId)
|
||||
{
|
||||
WorkflowSchedulerService schedulerService = this.Runtime.GetService(typeof(WorkflowSchedulerService)) as WorkflowSchedulerService;
|
||||
schedulerService.Schedule(callback, workflowInstanceId, whenUtc, timerId);
|
||||
}
|
||||
|
||||
public void CancelTimer(Guid timerId)
|
||||
{
|
||||
WorkflowSchedulerService schedulerService = this.Runtime.GetService(typeof(WorkflowSchedulerService)) as WorkflowSchedulerService;
|
||||
schedulerService.Cancel(timerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <copyright file="WorkflowTransactionService.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Transactions;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public abstract class WorkflowCommitWorkBatchService : WorkflowRuntimeService
|
||||
{
|
||||
public delegate void CommitWorkBatchCallback();
|
||||
|
||||
virtual internal protected void CommitWorkBatch(CommitWorkBatchCallback commitWorkBatchCallback)
|
||||
{
|
||||
Transaction tx = null;
|
||||
if (null == Transaction.Current)
|
||||
tx = new CommittableTransaction();
|
||||
else
|
||||
tx = Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
|
||||
|
||||
try
|
||||
{
|
||||
using (TransactionScope ts = new TransactionScope(tx))
|
||||
{
|
||||
commitWorkBatchCallback();
|
||||
ts.Complete();
|
||||
}
|
||||
|
||||
CommittableTransaction committableTransaction = tx as CommittableTransaction;
|
||||
if (committableTransaction != null)
|
||||
committableTransaction.Commit();
|
||||
|
||||
DependentTransaction dependentTransaction = tx as DependentTransaction;
|
||||
if (dependentTransaction != null)
|
||||
dependentTransaction.Complete();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
tx.Rollback(e);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (tx != null)
|
||||
{
|
||||
tx.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/*******************************************************************************
|
||||
// Copyright (C) 2000-2001 Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// CONTENTS
|
||||
// Workflow Web Hosting Module.
|
||||
|
||||
// DESCRIPTION
|
||||
// Implementation of Workflow Web Host Module.
|
||||
|
||||
// REVISIONS
|
||||
// Date Ver By Remarks
|
||||
// ~~~~~~~~~~ ~~~ ~~~~~~~~ ~~~~~~~~~~~~~~
|
||||
// 02/22/05 1.0 [....] Implementation.
|
||||
* ****************************************************************************/
|
||||
|
||||
#region Using directives
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Web;
|
||||
using System.Collections.Specialized;
|
||||
using System.Threading;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace System.Workflow.Runtime.Hosting
|
||||
{
|
||||
/// <summary>
|
||||
/// Cookie based rotuing module implementation
|
||||
/// </summary>
|
||||
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
||||
public sealed class WorkflowWebHostingModule : IHttpModule
|
||||
{
|
||||
HttpApplication currentApplication;
|
||||
|
||||
public WorkflowWebHostingModule()
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Workflow Web Hosting Module Created");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IHttpModule.Init()
|
||||
/// </summary>
|
||||
/// <param name="application"></param>
|
||||
void IHttpModule.Init(HttpApplication application)
|
||||
{
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "Workflow Web Hosting Module Initialized");
|
||||
|
||||
this.currentApplication = application;
|
||||
|
||||
//Listen for Acquire and ReleaseRequestState event
|
||||
application.ReleaseRequestState += this.OnReleaseRequestState;
|
||||
application.AcquireRequestState += this.OnAcquireRequestState;
|
||||
}
|
||||
|
||||
void IHttpModule.Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void OnAcquireRequestState(Object sender, EventArgs e)
|
||||
{
|
||||
//Performs Cookie based routing.
|
||||
WorkflowTrace.Host.TraceEvent(TraceEventType.Information, 0, "WebHost Module Routing Begin");
|
||||
|
||||
HttpCookie routingCookie = HttpContext.Current.Request.Cookies.Get("WF_WorkflowInstanceId");
|
||||
|
||||
if (routingCookie != null)
|
||||
{
|
||||
HttpContext.Current.Items.Add("__WorkflowInstanceId__", new Guid(routingCookie.Value));
|
||||
}
|
||||
//else no routing information found, it could be activation request or non workflow based request.
|
||||
}
|
||||
|
||||
void OnReleaseRequestState(Object sender, EventArgs e)
|
||||
{
|
||||
//Saves cookie back to client.
|
||||
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get("WF_WorkflowInstanceId");
|
||||
|
||||
if (cookie == null)
|
||||
{
|
||||
cookie = new HttpCookie("WF_WorkflowInstanceId");
|
||||
Object workflowInstanceId = HttpContext.Current.Items["__WorkflowInstanceId__"];
|
||||
|
||||
if (workflowInstanceId != null)
|
||||
{
|
||||
cookie.Value = workflowInstanceId.ToString();
|
||||
HttpContext.Current.Response.Cookies.Add(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user