From 13246fef1b691658068abe89e909f93b10b1d91c Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Sat, 15 Jul 2017 15:48:31 -0700 Subject: [PATCH 1/9] migrate from threads to TPL --- ...BackgroundWorker.cs => IBackgroundTask.cs} | 2 +- .../Interface/IDateTimeProvider.cs | 9 ++ src/WorkflowCore/Interface/IEventThread.cs | 7 - src/WorkflowCore/Interface/IRunnablePoller.cs | 7 - .../Interface/IWorkflowExecutor.cs | 2 +- src/WorkflowCore/Interface/IWorkflowThread.cs | 7 - src/WorkflowCore/Models/WorkflowOptions.cs | 9 +- .../ServiceCollectionExtensions.cs | 11 +- src/WorkflowCore/Services/DateTimeProvider.cs | 12 ++ .../Services/{EventThread.cs => EventTask.cs} | 82 +++++----- src/WorkflowCore/Services/RunnablePoller.cs | 2 +- src/WorkflowCore/Services/WorkflowExecutor.cs | 43 ++--- src/WorkflowCore/Services/WorkflowHost.cs | 33 ++-- src/WorkflowCore/Services/WorkflowTask.cs | 148 ++++++++++++++++++ src/WorkflowCore/Services/WorkflowThread.cs | 132 ---------------- src/WorkflowCore/WorkflowCore.csproj | 3 +- src/samples/WorkflowCore.Sample14/Program.cs | 14 +- .../RecurSampleWorkflow.cs | 2 +- .../Services/WorkflowExecutorFixture.cs | 2 +- 19 files changed, 274 insertions(+), 253 deletions(-) rename src/WorkflowCore/Interface/{IBackgroundWorker.cs => IBackgroundTask.cs} (70%) create mode 100644 src/WorkflowCore/Interface/IDateTimeProvider.cs delete mode 100644 src/WorkflowCore/Interface/IEventThread.cs delete mode 100644 src/WorkflowCore/Interface/IRunnablePoller.cs delete mode 100644 src/WorkflowCore/Interface/IWorkflowThread.cs create mode 100644 src/WorkflowCore/Services/DateTimeProvider.cs rename src/WorkflowCore/Services/{EventThread.cs => EventTask.cs} (61%) create mode 100644 src/WorkflowCore/Services/WorkflowTask.cs delete mode 100644 src/WorkflowCore/Services/WorkflowThread.cs diff --git a/src/WorkflowCore/Interface/IBackgroundWorker.cs b/src/WorkflowCore/Interface/IBackgroundTask.cs similarity index 70% rename from src/WorkflowCore/Interface/IBackgroundWorker.cs rename to src/WorkflowCore/Interface/IBackgroundTask.cs index 403107a0f..2ceecf953 100644 --- a/src/WorkflowCore/Interface/IBackgroundWorker.cs +++ b/src/WorkflowCore/Interface/IBackgroundTask.cs @@ -1,7 +1,7 @@  namespace WorkflowCore.Interface { - public interface IBackgroundWorker + public interface IBackgroundTask { void Start(); void Stop(); diff --git a/src/WorkflowCore/Interface/IDateTimeProvider.cs b/src/WorkflowCore/Interface/IDateTimeProvider.cs new file mode 100644 index 000000000..eec4026f3 --- /dev/null +++ b/src/WorkflowCore/Interface/IDateTimeProvider.cs @@ -0,0 +1,9 @@ +using System; + +namespace WorkflowCore.Interface +{ + public interface IDateTimeProvider + { + DateTime Now { get; } + } +} \ No newline at end of file diff --git a/src/WorkflowCore/Interface/IEventThread.cs b/src/WorkflowCore/Interface/IEventThread.cs deleted file mode 100644 index 7f53bd32e..000000000 --- a/src/WorkflowCore/Interface/IEventThread.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace WorkflowCore.Interface -{ - interface IEventThread : IBackgroundWorker - { - } -} \ No newline at end of file diff --git a/src/WorkflowCore/Interface/IRunnablePoller.cs b/src/WorkflowCore/Interface/IRunnablePoller.cs deleted file mode 100644 index 13c92db7c..000000000 --- a/src/WorkflowCore/Interface/IRunnablePoller.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace WorkflowCore.Interface -{ - interface IRunnablePoller : IBackgroundWorker - { - } -} \ No newline at end of file diff --git a/src/WorkflowCore/Interface/IWorkflowExecutor.cs b/src/WorkflowCore/Interface/IWorkflowExecutor.cs index 9cf9718a4..fc6531443 100644 --- a/src/WorkflowCore/Interface/IWorkflowExecutor.cs +++ b/src/WorkflowCore/Interface/IWorkflowExecutor.cs @@ -6,6 +6,6 @@ namespace WorkflowCore.Interface { public interface IWorkflowExecutor { - WorkflowExecutorResult Execute(WorkflowInstance workflow, WorkflowOptions options); + Task Execute(WorkflowInstance workflow, WorkflowOptions options); } } \ No newline at end of file diff --git a/src/WorkflowCore/Interface/IWorkflowThread.cs b/src/WorkflowCore/Interface/IWorkflowThread.cs deleted file mode 100644 index 1d272cc1c..000000000 --- a/src/WorkflowCore/Interface/IWorkflowThread.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace WorkflowCore.Interface -{ - interface IWorkflowThread : IBackgroundWorker - { - } -} \ No newline at end of file diff --git a/src/WorkflowCore/Models/WorkflowOptions.cs b/src/WorkflowCore/Models/WorkflowOptions.cs index 357a708f9..5c34aee70 100644 --- a/src/WorkflowCore/Models/WorkflowOptions.cs +++ b/src/WorkflowCore/Models/WorkflowOptions.cs @@ -12,7 +12,6 @@ public class WorkflowOptions internal Func PersistanceFactory; internal Func QueueFactory; internal Func LockFactory; - internal int ThreadCount; internal TimeSpan PollInterval; internal TimeSpan IdleTime; internal TimeSpan ErrorRetryInterval; @@ -20,9 +19,8 @@ public class WorkflowOptions public WorkflowOptions() { //set defaults - ThreadCount = Environment.ProcessorCount; PollInterval = TimeSpan.FromSeconds(10); - IdleTime = TimeSpan.FromMilliseconds(500); + IdleTime = TimeSpan.FromMilliseconds(100); ErrorRetryInterval = TimeSpan.FromSeconds(60); QueueFactory = new Func(sp => new SingleNodeQueueProvider()); @@ -45,11 +43,6 @@ public void UseQueueProvider(Func factory) QueueFactory = factory; } - public void UseThreads(int count) - { - ThreadCount = count; - } - public void UsePollInterval(TimeSpan interval) { PollInterval = interval; diff --git a/src/WorkflowCore/ServiceCollectionExtensions.cs b/src/WorkflowCore/ServiceCollectionExtensions.cs index 169504023..9a71d11e6 100644 --- a/src/WorkflowCore/ServiceCollectionExtensions.cs +++ b/src/WorkflowCore/ServiceCollectionExtensions.cs @@ -23,13 +23,16 @@ public static void AddWorkflow(this IServiceCollection services, Action(options.LockFactory); services.AddSingleton(); services.AddSingleton(options); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - + services.AddTransient(); + services.AddTransient(); } } diff --git a/src/WorkflowCore/Services/DateTimeProvider.cs b/src/WorkflowCore/Services/DateTimeProvider.cs new file mode 100644 index 000000000..7aab264f8 --- /dev/null +++ b/src/WorkflowCore/Services/DateTimeProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using WorkflowCore.Interface; + +namespace WorkflowCore.Services +{ + public class DateTimeProvider : IDateTimeProvider + { + public DateTime Now => DateTime.Now; + } +} diff --git a/src/WorkflowCore/Services/EventThread.cs b/src/WorkflowCore/Services/EventTask.cs similarity index 61% rename from src/WorkflowCore/Services/EventThread.cs rename to src/WorkflowCore/Services/EventTask.cs index ae5a4ce2e..8e8579502 100644 --- a/src/WorkflowCore/Services/EventThread.cs +++ b/src/WorkflowCore/Services/EventTask.cs @@ -10,36 +10,38 @@ namespace WorkflowCore.Services { - class EventThread : IEventThread + class EventTask : IBackgroundTask { private readonly IPersistenceProvider _persistenceStore; private readonly IDistributedLockProvider _lockProvider; private readonly IQueueProvider _queueProvider; private readonly ILogger _logger; private readonly WorkflowOptions _options; + private readonly Task _task; + private readonly IDateTimeProvider _datetimeProvider; private bool _shutdown = true; - private Thread _thread; - public EventThread(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, WorkflowOptions options) + public EventTask(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, WorkflowOptions options, IDateTimeProvider datetimeProvider) { _persistenceStore = persistenceStore; _queueProvider = queueProvider; _options = options; - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); _lockProvider = lockProvider; - _thread = new Thread(RunEvents); + _datetimeProvider = datetimeProvider; + _task = new Task(RunEvents); } public void Start() { _shutdown = false; - _thread.Start(); + _task.Start(); } public void Stop() { _shutdown = true; - _thread.Join(); + _task.Wait(); } private async void RunEvents() @@ -50,50 +52,52 @@ private async void RunEvents() { var eventId = await _queueProvider.DequeueWork(QueueType.Event); if (eventId != null) + Parallel.Invoke(() => ProcessEvent(eventId)); + else + await Task.Delay(_options.IdleTime); //no work + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + } + } + } + + private async void ProcessEvent(string eventId) + { + try + { + if (await _lockProvider.AcquireLock($"evt:{eventId}")) + { + try { - if (await _lockProvider.AcquireLock($"evt:{eventId}")) + var evt = await _persistenceStore.GetEvent(eventId); + if (evt.EventTime <= _datetimeProvider.Now.ToUniversalTime()) { - try - { - var evt = await _persistenceStore.GetEvent(eventId); - if (evt.EventTime <= DateTime.Now.ToUniversalTime()) - { - var subs = await _persistenceStore.GetSubcriptions(evt.EventName, evt.EventKey, evt.EventTime); - var success = true; + var subs = await _persistenceStore.GetSubcriptions(evt.EventName, evt.EventKey, evt.EventTime); + var success = true; - foreach (var sub in subs.ToList()) - success = success && await SeedSubscription(evt, sub); + foreach (var sub in subs.ToList()) + success = success && await SeedSubscription(evt, sub); - if (success) - await _persistenceStore.MarkEventProcessed(eventId); - } - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - } - finally - { - await _lockProvider.ReleaseLock($"evt:{eventId}"); - } - } - else - { - _logger.LogInformation($"Event locked {eventId}"); + if (success) + await _persistenceStore.MarkEventProcessed(eventId); } - } - else + finally { - await Task.Delay(_options.IdleTime); //no work + await _lockProvider.ReleaseLock($"evt:{eventId}"); } - } - catch (Exception ex) + else { - _logger.LogError(ex.Message); + _logger.LogInformation($"Event locked {eventId}"); } } + catch (Exception ex) + { + _logger.LogError(ex.Message); + } } private async Task SeedSubscription(Event evt, EventSubscription sub) diff --git a/src/WorkflowCore/Services/RunnablePoller.cs b/src/WorkflowCore/Services/RunnablePoller.cs index e79aaeb10..320655b3b 100644 --- a/src/WorkflowCore/Services/RunnablePoller.cs +++ b/src/WorkflowCore/Services/RunnablePoller.cs @@ -9,7 +9,7 @@ namespace WorkflowCore.Services { - class RunnablePoller : IRunnablePoller + class RunnablePoller : IBackgroundTask { private readonly IPersistenceProvider _persistenceStore; private readonly IDistributedLockProvider _lockProvider; diff --git a/src/WorkflowCore/Services/WorkflowExecutor.cs b/src/WorkflowCore/Services/WorkflowExecutor.cs index 39282eb83..63adb74cb 100644 --- a/src/WorkflowCore/Services/WorkflowExecutor.cs +++ b/src/WorkflowCore/Services/WorkflowExecutor.cs @@ -5,6 +5,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using WorkflowCore.Interface; using WorkflowCore.Models; @@ -12,25 +13,27 @@ namespace WorkflowCore.Services { public class WorkflowExecutor : IWorkflowExecutor { - - protected readonly IWorkflowHost _host; + protected readonly IWorkflowRegistry _registry; protected readonly IServiceProvider _serviceProvider; + protected readonly IDateTimeProvider _datetimeProvider; protected readonly ILogger _logger; - public WorkflowExecutor(IWorkflowHost host, IWorkflowRegistry registry, IServiceProvider serviceProvider, ILoggerFactory loggerFactory) + private IWorkflowHost Host => _serviceProvider.GetService(); + + public WorkflowExecutor(IWorkflowRegistry registry, IServiceProvider serviceProvider, IDateTimeProvider datetimeProvider, ILoggerFactory loggerFactory) { - _host = host; _serviceProvider = serviceProvider; _registry = registry; + _datetimeProvider = datetimeProvider; _logger = loggerFactory.CreateLogger(); } - public WorkflowExecutorResult Execute(WorkflowInstance workflow, WorkflowOptions options) + public async Task Execute(WorkflowInstance workflow, WorkflowOptions options) { - WorkflowExecutorResult wfResult = new WorkflowExecutorResult(); + var wfResult = new WorkflowExecutorResult(); - List exePointers = new List(workflow.ExecutionPointers.Where(x => x.Active && (!x.SleepUntil.HasValue || x.SleepUntil < DateTime.Now.ToUniversalTime()))); + var exePointers = new List(workflow.ExecutionPointers.Where(x => x.Active && (!x.SleepUntil.HasValue || x.SleepUntil < _datetimeProvider.Now.ToUniversalTime()))); var def = _registry.GetDefinition(workflow.WorkflowDefinitionId, workflow.Version); if (def == null) { @@ -51,12 +54,12 @@ public WorkflowExecutorResult Execute(WorkflowInstance workflow, WorkflowOptions continue; case ExecutionPipelineDirective.EndWorkflow: workflow.Status = WorkflowStatus.Complete; - workflow.CompleteTime = DateTime.Now.ToUniversalTime(); + workflow.CompleteTime = _datetimeProvider.Now.ToUniversalTime(); continue; } if (!pointer.StartTime.HasValue) - pointer.StartTime = DateTime.Now; + pointer.StartTime = _datetimeProvider.Now.ToUniversalTime(); _logger.LogDebug("Starting step {0} on workflow {1}", step.Name, workflow.Id); @@ -65,12 +68,12 @@ public WorkflowExecutorResult Execute(WorkflowInstance workflow, WorkflowOptions if (body == null) { _logger.LogError("Unable to construct step body {0}", step.BodyType.ToString()); - pointer.SleepUntil = DateTime.Now.ToUniversalTime().Add(options.ErrorRetryInterval); + pointer.SleepUntil = _datetimeProvider.Now.ToUniversalTime().Add(options.ErrorRetryInterval); wfResult.Errors.Add(new ExecutionError() { WorkflowId = workflow.Id, ExecutionPointerId = pointer.Id, - ErrorTime = DateTime.Now.ToUniversalTime(), + ErrorTime = _datetimeProvider.Now.ToUniversalTime(), Message = String.Format("Unable to construct step body {0}", step.BodyType.ToString()) }); continue; @@ -93,7 +96,7 @@ public WorkflowExecutorResult Execute(WorkflowInstance workflow, WorkflowOptions continue; case ExecutionPipelineDirective.EndWorkflow: workflow.Status = WorkflowStatus.Complete; - workflow.CompleteTime = DateTime.Now.ToUniversalTime(); + workflow.CompleteTime = _datetimeProvider.Now.ToUniversalTime(); continue; } @@ -111,14 +114,14 @@ public WorkflowExecutorResult Execute(WorkflowInstance workflow, WorkflowOptions { WorkflowId = workflow.Id, ExecutionPointerId = pointer.Id, - ErrorTime = DateTime.Now.ToUniversalTime(), + ErrorTime = _datetimeProvider.Now.ToUniversalTime(), Message = ex.Message }); switch (step.ErrorBehavior ?? def.DefaultErrorBehavior) { case WorkflowErrorHandling.Retry: - pointer.SleepUntil = DateTime.Now.ToUniversalTime().Add(step.RetryInterval ?? def.DefaultErrorRetryInterval ?? options.ErrorRetryInterval); + pointer.SleepUntil = _datetimeProvider.Now.ToUniversalTime().Add(step.RetryInterval ?? def.DefaultErrorRetryInterval ?? options.ErrorRetryInterval); break; case WorkflowErrorHandling.Suspend: workflow.Status = WorkflowStatus.Suspended; @@ -128,18 +131,18 @@ public WorkflowExecutorResult Execute(WorkflowInstance workflow, WorkflowOptions break; } - _host.ReportStepError(workflow, step, ex); + Host.ReportStepError(workflow, step, ex); } } else { _logger.LogError("Unable to find step {0} in workflow definition", pointer.StepId); - pointer.SleepUntil = DateTime.Now.ToUniversalTime().Add(options.ErrorRetryInterval); + pointer.SleepUntil = _datetimeProvider.Now.ToUniversalTime().Add(options.ErrorRetryInterval); wfResult.Errors.Add(new ExecutionError() { WorkflowId = workflow.Id, ExecutionPointerId = pointer.Id, - ErrorTime = DateTime.Now.ToUniversalTime(), + ErrorTime = _datetimeProvider.Now.ToUniversalTime(), Message = String.Format("Unable to find step {0} in workflow definition", pointer.StepId) }); } @@ -157,7 +160,7 @@ private void ProcessExecutionResult(WorkflowInstance workflow, WorkflowDefinitio pointer.PersistenceData = result.PersistenceData; pointer.Outcome = result.OutcomeValue; if (result.SleepFor.HasValue) - pointer.SleepUntil = DateTime.Now.ToUniversalTime().Add(result.SleepFor.Value); + pointer.SleepUntil = _datetimeProvider.Now.ToUniversalTime().Add(result.SleepFor.Value); if (!string.IsNullOrEmpty(result.EventName)) { @@ -178,7 +181,7 @@ private void ProcessExecutionResult(WorkflowInstance workflow, WorkflowDefinitio if (result.Proceed) { pointer.Active = false; - pointer.EndTime = DateTime.Now.ToUniversalTime(); + pointer.EndTime = _datetimeProvider.Now.ToUniversalTime(); foreach (var outcomeTarget in step.Outcomes.Where(x => object.Equals(x.GetValue(workflow.Data), result.OutcomeValue) || x.GetValue(workflow.Data) == null)) { @@ -301,7 +304,7 @@ private void DetermineNextExecutionTime(WorkflowInstance workflow) if ((workflow.NextExecution == null) && (workflow.ExecutionPointers.All(x => x.EndTime != null))) { workflow.Status = WorkflowStatus.Complete; - workflow.CompleteTime = DateTime.Now.ToUniversalTime(); + workflow.CompleteTime = _datetimeProvider.Now.ToUniversalTime(); } } diff --git a/src/WorkflowCore/Services/WorkflowHost.cs b/src/WorkflowCore/Services/WorkflowHost.cs index 744cb6802..4fcffb3a9 100644 --- a/src/WorkflowCore/Services/WorkflowHost.cs +++ b/src/WorkflowCore/Services/WorkflowHost.cs @@ -13,10 +13,11 @@ namespace WorkflowCore.Services { public class WorkflowHost : IWorkflowHost, IDisposable { - protected List _workers = new List(); protected bool _shutdown = true; protected IServiceProvider _serviceProvider; + private readonly IEnumerable _backgroundTasks; + public event StepErrorEventHandler OnStepError; //public dependencies to allow for extension method access @@ -27,7 +28,7 @@ public class WorkflowHost : IWorkflowHost, IDisposable public IQueueProvider QueueProvider { get; private set; } public ILogger Logger { get; private set; } - public WorkflowHost(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, WorkflowOptions options, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider) + public WorkflowHost(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, WorkflowOptions options, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, IEnumerable backgroundTasks) { PersistenceStore = persistenceStore; QueueProvider = queueProvider; @@ -36,6 +37,7 @@ public WorkflowHost(IPersistenceProvider persistenceStore, IQueueProvider queueP _serviceProvider = serviceProvider; Registry = registry; LockProvider = lockProvider; + _backgroundTasks = backgroundTasks; persistenceStore.EnsureStoreExists(); } @@ -94,35 +96,22 @@ public void Start() _shutdown = false; QueueProvider.Start().Wait(); LockProvider.Start().Wait(); - for (int i = 0; i < Options.ThreadCount; i++) - { - Logger.LogInformation("Starting worker thread #{0}", i); - IWorkflowThread thread = _serviceProvider.GetService(); - _workers.Add(thread); - thread.Start(); - } - Logger.LogInformation("Starting publish thread"); - IEventThread pubThread = _serviceProvider.GetService(); - _workers.Add(pubThread); - pubThread.Start(); + Logger.LogInformation("Starting backgroud tasks"); - Logger.LogInformation("Starting poller"); - IRunnablePoller poller = _serviceProvider.GetService(); - _workers.Add(poller); - poller.Start(); + foreach (var task in _backgroundTasks) + task.Start(); } public void Stop() { _shutdown = true; - Logger.LogInformation("Stopping worker threads"); - foreach (var th in _workers) + Logger.LogInformation("Stopping background tasks"); + foreach (var th in _backgroundTasks) th.Stop(); - - _workers.Clear(); - Logger.LogInformation("Worker threads stopped"); + + Logger.LogInformation("Worker tasks stopped"); QueueProvider.Stop(); LockProvider.Stop(); diff --git a/src/WorkflowCore/Services/WorkflowTask.cs b/src/WorkflowCore/Services/WorkflowTask.cs new file mode 100644 index 000000000..659c60a21 --- /dev/null +++ b/src/WorkflowCore/Services/WorkflowTask.cs @@ -0,0 +1,148 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using WorkflowCore.Interface; +using WorkflowCore.Models; + +namespace WorkflowCore.Services +{ + class WorkflowTask : IBackgroundTask + { + private readonly IPersistenceProvider _persistenceStore; + private readonly IDistributedLockProvider _lockProvider; + private readonly IWorkflowExecutor _executor; + private readonly IQueueProvider _queueProvider; + private readonly ILogger _logger; + private readonly Task _task; + private readonly WorkflowOptions _options; + private readonly IDateTimeProvider _datetimeProvider; + private bool _shutdown = true; + + public WorkflowTask(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, IWorkflowExecutor executor, IDateTimeProvider datetimeProvider, WorkflowOptions options) + { + _persistenceStore = persistenceStore; + _queueProvider = queueProvider; + _executor = executor; + _options = options; + _logger = loggerFactory.CreateLogger(); + _lockProvider = lockProvider; + _task = new Task(RunWorkflows); + _datetimeProvider = datetimeProvider; + persistenceStore.EnsureStoreExists(); + } + + public void Start() + { + _shutdown = false; + _task.Start(); + } + + public void Stop() + { + _shutdown = true; + _task.Wait(); + } + + /// + /// Worker task body + /// + private async void RunWorkflows() + { + while (!_shutdown) + { + try + { + var workflowId = await _queueProvider.DequeueWork(QueueType.Workflow); + if (workflowId != null) + Parallel.Invoke(() => ProcessWorkflow(workflowId)); + else + await Task.Delay(_options.IdleTime); //no work + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + } + } + } + + private async void ProcessWorkflow(string workflowId) + { + try + { + if (await _lockProvider.AcquireLock(workflowId)) + { + WorkflowInstance workflow = null; + WorkflowExecutorResult result = null; + try + { + workflow = await _persistenceStore.GetWorkflowInstance(workflowId); + if (workflow.Status == WorkflowStatus.Runnable) + { + try + { + result = await _executor.Execute(workflow, _options); + } + finally + { + await _persistenceStore.PersistWorkflow(workflow); + } + } + } + finally + { + await _lockProvider.ReleaseLock(workflowId); + if ((workflow != null) && (result != null)) + { + foreach (var sub in result.Subscriptions) + await SubscribeEvent(sub); + + await _persistenceStore.PersistErrors(result.Errors); + + var readAheadTicks = _datetimeProvider.Now.Add(_options.PollInterval).ToUniversalTime().Ticks; + + if ((workflow.Status == WorkflowStatus.Runnable) && workflow.NextExecution.HasValue && workflow.NextExecution.Value < readAheadTicks) + { + Parallel.Invoke(async () => + { + if (!workflow.NextExecution.HasValue) + return; + + var target = (workflow.NextExecution.Value - _datetimeProvider.Now.ToUniversalTime().Ticks); + if (target > 0) + await Task.Delay(TimeSpan.FromTicks(target)); + + await _queueProvider.QueueWork(workflowId, QueueType.Workflow); + }); + } + } + } + } + else + { + _logger.LogInformation("Workflow locked {0}", workflowId); + } + } + catch (Exception ex) + { + _logger.LogError(ex.Message); + } + } + + private async Task SubscribeEvent(EventSubscription subscription) + { + //TODO: move to own class + _logger.LogDebug("Subscribing to event {0} {1} for workflow {2} step {3}", subscription.EventName, subscription.EventKey, subscription.WorkflowId, subscription.StepId); + + await _persistenceStore.CreateEventSubscription(subscription); + var events = await _persistenceStore.GetEvents(subscription.EventName, subscription.EventKey, subscription.SubscribeAsOf); + foreach (var evt in events) + { + await _persistenceStore.MarkEventUnprocessed(evt); + await _queueProvider.QueueWork(evt, QueueType.Event); + } + } + } +} diff --git a/src/WorkflowCore/Services/WorkflowThread.cs b/src/WorkflowCore/Services/WorkflowThread.cs deleted file mode 100644 index babe6c356..000000000 --- a/src/WorkflowCore/Services/WorkflowThread.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using WorkflowCore.Interface; -using WorkflowCore.Models; - -namespace WorkflowCore.Services -{ - class WorkflowThread : IWorkflowThread - { - private readonly IPersistenceProvider _persistenceStore; - private readonly IDistributedLockProvider _lockProvider; - private readonly IWorkflowExecutor _executor; - private readonly IQueueProvider _queueProvider; - private readonly ILogger _logger; - private readonly WorkflowOptions _options; - private bool _shutdown = true; - private Thread _thread; - - public WorkflowThread(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, IWorkflowExecutor executor, WorkflowOptions options) - { - _persistenceStore = persistenceStore; - _queueProvider = queueProvider; - _executor = executor; - _options = options; - _logger = loggerFactory.CreateLogger(); - _lockProvider = lockProvider; - _thread = new Thread(RunWorkflows); - persistenceStore.EnsureStoreExists(); - } - - public void Start() - { - _shutdown = false; - _thread.Start(); - } - - public void Stop() - { - _shutdown = true; - _thread.Join(); - } - - /// - /// Worker thread body - /// - private async void RunWorkflows() - { - while (!_shutdown) - { - try - { - var workflowId = await _queueProvider.DequeueWork(QueueType.Workflow); - if (workflowId != null) - { - try - { - if (await _lockProvider.AcquireLock(workflowId)) - { - WorkflowInstance workflow = null; - WorkflowExecutorResult result = null; - try - { - workflow = await _persistenceStore.GetWorkflowInstance(workflowId); - if (workflow.Status == WorkflowStatus.Runnable) - { - try - { - result = _executor.Execute(workflow, _options); - } - finally - { - await _persistenceStore.PersistWorkflow(workflow); - } - } - } - finally - { - await _lockProvider.ReleaseLock(workflowId); - if ((workflow != null) && (result != null)) - { - foreach (var sub in result.Subscriptions) - await SubscribeEvent(sub); - - await _persistenceStore.PersistErrors(result.Errors); - - if ((workflow.Status == WorkflowStatus.Runnable) && workflow.NextExecution.HasValue && workflow.NextExecution.Value < DateTime.Now.ToUniversalTime().Ticks) - await _queueProvider.QueueWork(workflowId, QueueType.Workflow); - } - } - } - else - { - _logger.LogInformation("Workflow locked {0}", workflowId); - } - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - } - } - else - { - await Task.Delay(_options.IdleTime); //no work - } - - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - } - } - } - - private async Task SubscribeEvent(EventSubscription subscription) - { - //TODO: move to own class - _logger.LogDebug("Subscribing to event {0} {1} for workflow {2} step {3}", subscription.EventName, subscription.EventKey, subscription.WorkflowId, subscription.StepId); - - await _persistenceStore.CreateEventSubscription(subscription); - var events = await _persistenceStore.GetEvents(subscription.EventName, subscription.EventKey, subscription.SubscribeAsOf); - foreach (var evt in events) - { - await _persistenceStore.MarkEventUnprocessed(evt); - await _queueProvider.QueueWork(evt, QueueType.Event); - } - } - } -} diff --git a/src/WorkflowCore/WorkflowCore.csproj b/src/WorkflowCore/WorkflowCore.csproj index 8919eda6c..b149c8955 100644 --- a/src/WorkflowCore/WorkflowCore.csproj +++ b/src/WorkflowCore/WorkflowCore.csproj @@ -12,7 +12,7 @@ https://github.com/danielgerlag/workflow-core/blob/master/LICENSE.md git https://github.com/danielgerlag/workflow-core.git - 1.6.0 + 1.6.1 $(PackageTargetFallback);dnxcore50 false false @@ -27,6 +27,7 @@ + diff --git a/src/samples/WorkflowCore.Sample14/Program.cs b/src/samples/WorkflowCore.Sample14/Program.cs index fb4f7f561..910985e1c 100644 --- a/src/samples/WorkflowCore.Sample14/Program.cs +++ b/src/samples/WorkflowCore.Sample14/Program.cs @@ -1,6 +1,7 @@ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using WorkflowCore.Interface; namespace WorkflowCore.Sample14 { @@ -8,7 +9,18 @@ class Program { static void Main(string[] args) { - Console.WriteLine("Hello World!"); + var serviceProvider = ConfigureServices(); + + //start the workflow host + var host = serviceProvider.GetService(); + host.RegisterWorkflow(); + host.Start(); + + Console.WriteLine("Starting workflow..."); + var workflowId = host.StartWorkflow("recur-sample").Result; + + Console.ReadLine(); + host.Stop(); } private static IServiceProvider ConfigureServices() diff --git a/src/samples/WorkflowCore.Sample14/RecurSampleWorkflow.cs b/src/samples/WorkflowCore.Sample14/RecurSampleWorkflow.cs index b31a2e6a2..d3d91f05b 100644 --- a/src/samples/WorkflowCore.Sample14/RecurSampleWorkflow.cs +++ b/src/samples/WorkflowCore.Sample14/RecurSampleWorkflow.cs @@ -15,7 +15,7 @@ public void Build(IWorkflowBuilder builder) { builder .StartWith(context => Console.WriteLine("Hello")) - .Recur(data => TimeSpan.FromMinutes(30), data => data.Counter > 5).Do(recur => recur + .Recur(data => TimeSpan.FromSeconds(5), data => data.Counter > 5).Do(recur => recur .StartWith(context => Console.WriteLine("Doing recurring task")) ) .Then(context => Console.WriteLine("Carry on")); diff --git a/test/WorkflowCore.UnitTests/Services/WorkflowExecutorFixture.cs b/test/WorkflowCore.UnitTests/Services/WorkflowExecutorFixture.cs index b4df19b58..b866c756a 100644 --- a/test/WorkflowCore.UnitTests/Services/WorkflowExecutorFixture.cs +++ b/test/WorkflowCore.UnitTests/Services/WorkflowExecutorFixture.cs @@ -80,7 +80,7 @@ public WorkflowExecutorFixture() Registry = serviceProvider.GetService(); - Subject = new WorkflowExecutor(Host, Registry, serviceProvider, loggerFactory); + Subject = new WorkflowExecutor(Host, Registry, serviceProvider, new DateTimeProvider(), loggerFactory); } [Fact] From 9ba9fbfd3ab35c25ab5431531753482d8e302883 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Sat, 15 Jul 2017 16:05:53 -0700 Subject: [PATCH 2/9] async steps --- src/WorkflowCore/Interface/IStepBody.cs | 2 +- src/WorkflowCore/Models/StepBody.cs | 7 ++++++- src/WorkflowCore/Models/StepBodyAsync.cs | 14 ++++++++++++++ src/WorkflowCore/Services/WorkflowExecutor.cs | 2 +- .../Services/WorkflowExecutorFixture.cs | 2 +- 5 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 src/WorkflowCore/Models/StepBodyAsync.cs diff --git a/src/WorkflowCore/Interface/IStepBody.cs b/src/WorkflowCore/Interface/IStepBody.cs index bceda9aa9..598d3b469 100644 --- a/src/WorkflowCore/Interface/IStepBody.cs +++ b/src/WorkflowCore/Interface/IStepBody.cs @@ -8,6 +8,6 @@ namespace WorkflowCore.Interface { public interface IStepBody { - ExecutionResult Run(IStepExecutionContext context); + Task RunAsync(IStepExecutionContext context); } } diff --git a/src/WorkflowCore/Models/StepBody.cs b/src/WorkflowCore/Models/StepBody.cs index 449a93ce6..dda4e8bbe 100644 --- a/src/WorkflowCore/Models/StepBody.cs +++ b/src/WorkflowCore/Models/StepBody.cs @@ -8,9 +8,14 @@ namespace WorkflowCore.Models { public abstract class StepBody : IStepBody { - + public abstract ExecutionResult Run(IStepExecutionContext context); + public Task RunAsync(IStepExecutionContext context) + { + return Task.FromResult(Run(context)); + } + protected ExecutionResult OutcomeResult(object value) { return new ExecutionResult() diff --git a/src/WorkflowCore/Models/StepBodyAsync.cs b/src/WorkflowCore/Models/StepBodyAsync.cs new file mode 100644 index 000000000..746d1a314 --- /dev/null +++ b/src/WorkflowCore/Models/StepBodyAsync.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using WorkflowCore.Interface; + +namespace WorkflowCore.Models +{ + public abstract class StepBodyAsync : IStepBody + { + public abstract Task RunAsync(IStepExecutionContext context); + + } +} diff --git a/src/WorkflowCore/Services/WorkflowExecutor.cs b/src/WorkflowCore/Services/WorkflowExecutor.cs index 63adb74cb..c1ce8309c 100644 --- a/src/WorkflowCore/Services/WorkflowExecutor.cs +++ b/src/WorkflowCore/Services/WorkflowExecutor.cs @@ -100,7 +100,7 @@ public async Task Execute(WorkflowInstance workflow, Wor continue; } - var result = body.Run(context); + var result = await body.RunAsync(context); ProcessOutputs(workflow, step, body); ProcessExecutionResult(workflow, def, pointer, step, result, wfResult); diff --git a/test/WorkflowCore.UnitTests/Services/WorkflowExecutorFixture.cs b/test/WorkflowCore.UnitTests/Services/WorkflowExecutorFixture.cs index b866c756a..735fb206d 100644 --- a/test/WorkflowCore.UnitTests/Services/WorkflowExecutorFixture.cs +++ b/test/WorkflowCore.UnitTests/Services/WorkflowExecutorFixture.cs @@ -80,7 +80,7 @@ public WorkflowExecutorFixture() Registry = serviceProvider.GetService(); - Subject = new WorkflowExecutor(Host, Registry, serviceProvider, new DateTimeProvider(), loggerFactory); + Subject = new WorkflowExecutor(Registry, serviceProvider, new DateTimeProvider(), loggerFactory); } [Fact] From a0f929edff2579fb9256f1e63ec3ebca4e5de6a5 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Sun, 16 Jul 2017 15:16:13 -0700 Subject: [PATCH 3/9] tpl refinements --- src/WorkflowCore/Models/WorkflowOptions.cs | 2 +- src/WorkflowCore/Services/WorkflowTask.cs | 19 +++++++++++++------ src/WorkflowCore/WorkflowCore.csproj | 6 +++--- .../WorkflowCore.Users.csproj | 6 +++--- src/samples/WorkflowCore.Sample03/Program.cs | 1 + .../WorkflowCore.Sample03/Steps/AddNumbers.cs | 4 ++-- src/samples/WorkflowCore.Sample13/Program.cs | 2 +- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/WorkflowCore/Models/WorkflowOptions.cs b/src/WorkflowCore/Models/WorkflowOptions.cs index 5c34aee70..ca6cd14ee 100644 --- a/src/WorkflowCore/Models/WorkflowOptions.cs +++ b/src/WorkflowCore/Models/WorkflowOptions.cs @@ -20,7 +20,7 @@ public WorkflowOptions() { //set defaults PollInterval = TimeSpan.FromSeconds(10); - IdleTime = TimeSpan.FromMilliseconds(100); + IdleTime = TimeSpan.FromMilliseconds(500); ErrorRetryInterval = TimeSpan.FromSeconds(60); QueueFactory = new Func(sp => new SingleNodeQueueProvider()); diff --git a/src/WorkflowCore/Services/WorkflowTask.cs b/src/WorkflowCore/Services/WorkflowTask.cs index 659c60a21..7255a1df1 100644 --- a/src/WorkflowCore/Services/WorkflowTask.cs +++ b/src/WorkflowCore/Services/WorkflowTask.cs @@ -16,7 +16,7 @@ class WorkflowTask : IBackgroundTask private readonly IWorkflowExecutor _executor; private readonly IQueueProvider _queueProvider; private readonly ILogger _logger; - private readonly Task _task; + private readonly IList _tasks; private readonly WorkflowOptions _options; private readonly IDateTimeProvider _datetimeProvider; private bool _shutdown = true; @@ -29,7 +29,11 @@ public WorkflowTask(IPersistenceProvider persistenceStore, IQueueProvider queueP _options = options; _logger = loggerFactory.CreateLogger(); _lockProvider = lockProvider; - _task = new Task(RunWorkflows); + + _tasks = new List(); + for (int i = 0; i < Environment.ProcessorCount; i++) + _tasks.Add(new Task(RunWorkflows)); + _datetimeProvider = datetimeProvider; persistenceStore.EnsureStoreExists(); } @@ -37,13 +41,15 @@ public WorkflowTask(IPersistenceProvider persistenceStore, IQueueProvider queueP public void Start() { _shutdown = false; - _task.Start(); + foreach (var task in _tasks) + task.Start(); } public void Stop() { _shutdown = true; - _task.Wait(); + foreach (var task in _tasks) + task.Wait(); } /// @@ -56,8 +62,9 @@ private async void RunWorkflows() try { var workflowId = await _queueProvider.DequeueWork(QueueType.Workflow); + if (workflowId != null) - Parallel.Invoke(() => ProcessWorkflow(workflowId)); + await ProcessWorkflow(workflowId); else await Task.Delay(_options.IdleTime); //no work } @@ -68,7 +75,7 @@ private async void RunWorkflows() } } - private async void ProcessWorkflow(string workflowId) + private async Task ProcessWorkflow(string workflowId) { try { diff --git a/src/WorkflowCore/WorkflowCore.csproj b/src/WorkflowCore/WorkflowCore.csproj index b149c8955..6bb04f1c2 100644 --- a/src/WorkflowCore/WorkflowCore.csproj +++ b/src/WorkflowCore/WorkflowCore.csproj @@ -18,9 +18,9 @@ false false Workflow Core is a light weight workflow engine targeting .NET Standard. - 1.2.9 - 1.2.9.0 - 1.2.9.0 + 1.3.0 + 1.3.0.0 + 1.3.0.0 diff --git a/src/extensions/WorkflowCore.Users/WorkflowCore.Users.csproj b/src/extensions/WorkflowCore.Users/WorkflowCore.Users.csproj index 4dd271053..59dfd0151 100644 --- a/src/extensions/WorkflowCore.Users/WorkflowCore.Users.csproj +++ b/src/extensions/WorkflowCore.Users/WorkflowCore.Users.csproj @@ -18,9 +18,9 @@ false false Provides extensions for Workflow Core to enable human workflows. - 1.2.9 - 1.2.9.0 - 1.2.9.0 + 1.3.0 + 1.3.0.0 + 1.3.0.0 diff --git a/src/samples/WorkflowCore.Sample03/Program.cs b/src/samples/WorkflowCore.Sample03/Program.cs index fe7b07fe8..776aa6df4 100644 --- a/src/samples/WorkflowCore.Sample03/Program.cs +++ b/src/samples/WorkflowCore.Sample03/Program.cs @@ -26,6 +26,7 @@ public static void Main(string[] args) initialData.Value2 = 3; host.StartWorkflow("PassingDataWorkflow", 1, initialData); + host.StartWorkflow("PassingDataWorkflow", 1, new MyDataClass() { Value1 = 3, Value2 = 3 }); Console.ReadLine(); host.Stop(); diff --git a/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs b/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs index 0ca1992b7..209229812 100644 --- a/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs +++ b/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs @@ -7,7 +7,7 @@ namespace WorkflowCore.Sample03.Steps { - public class AddNumbers : StepBody + public class AddNumbers : StepBodyAsync { public int Input1 { get; set; } @@ -16,7 +16,7 @@ public class AddNumbers : StepBody public int Output { get; set; } - public override ExecutionResult Run(IStepExecutionContext context) + public override async Task RunAsync(IStepExecutionContext context) { Output = (Input1 + Input2); return ExecutionResult.Next(); diff --git a/src/samples/WorkflowCore.Sample13/Program.cs b/src/samples/WorkflowCore.Sample13/Program.cs index 73b680d68..5a9263579 100644 --- a/src/samples/WorkflowCore.Sample13/Program.cs +++ b/src/samples/WorkflowCore.Sample13/Program.cs @@ -17,7 +17,7 @@ public static void Main(string[] args) host.Start(); Console.WriteLine("Starting workflow..."); - string workflowId = host.StartWorkflow("parallel-sample").Result; + host.StartWorkflow("parallel-sample"); Console.ReadLine(); host.Stop(); From c1a2a86e8da1563d0d772fac97d0cc76c752fdb5 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Sun, 16 Jul 2017 17:54:30 -0700 Subject: [PATCH 4/9] samples --- src/samples/WorkflowCore.Sample03/PassingDataWorkflow.cs | 2 +- src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/samples/WorkflowCore.Sample03/PassingDataWorkflow.cs b/src/samples/WorkflowCore.Sample03/PassingDataWorkflow.cs index a5ec65aae..c96f3dc03 100644 --- a/src/samples/WorkflowCore.Sample03/PassingDataWorkflow.cs +++ b/src/samples/WorkflowCore.Sample03/PassingDataWorkflow.cs @@ -27,7 +27,7 @@ public void Build(IWorkflowBuilder builder) .Input(step => step.Message, data => "The answer is " + data.Value3.ToString()) .Then(context => { - Console.WriteLine("Workflow comeplete"); + Console.WriteLine("Workflow complete"); return ExecutionResult.Next(); }); } diff --git a/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs b/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs index 209229812..104fd3b5e 100644 --- a/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs +++ b/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs @@ -18,7 +18,11 @@ public class AddNumbers : StepBodyAsync public override async Task RunAsync(IStepExecutionContext context) { - Output = (Input1 + Input2); + if (Input1 == 2) + await Task.Delay(5000); + //System.Threading.Thread.Sleep(2000); + + Output = (Input1 + Input2); return ExecutionResult.Next(); } } From 2a345370d2d8cd4e1213d3c2814202658ce417d1 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Mon, 17 Jul 2017 19:51:41 -0700 Subject: [PATCH 5/9] TPL migration --- .../Interface/IDistributedLockProvider.cs | 3 +- src/WorkflowCore/Interface/IQueueProvider.cs | 5 +- src/WorkflowCore/Models/WorkflowOptions.cs | 2 +- src/WorkflowCore/Models/WorkflowStep.cs | 1 - .../ServiceCollectionExtensions.cs | 4 +- src/WorkflowCore/Services/EventTask.cs | 140 ------------ .../Services/EventTaskDispatcher.cs | 97 ++++++++ .../Services/QueueTaskDispatcher.cs | 114 +++++++++ src/WorkflowCore/Services/RunnablePoller.cs | 4 +- .../Services/SingleNodeLockProvider.cs | 2 +- .../Services/SingleNodeQueueProvider.cs | 16 +- src/WorkflowCore/Services/WorkflowHost.cs | 6 +- src/WorkflowCore/Services/WorkflowTask.cs | 155 ------------- .../Services/WorkflowTaskDispatcher.cs | 111 +++++++++ .../Services/RedlockProvider.cs | 2 +- .../WorkflowCore.LockProviders.Redlock.csproj | 4 +- .../SqlLockProvider.cs | 6 +- ...orkflowCore.LockProviders.SqlServer.csproj | 1 + .../Services/ZeroMQLockProvider.cs | 3 +- .../WorkflowCore.LockProviders.ZeroMQ.csproj | 6 +- .../EntityFrameworkPersistenceProvider.cs | 216 ++++++++++++------ ...lowCore.Persistence.EntityFramework.csproj | 4 +- .../Services/MongoPersistenceProvider.cs | 75 +++--- .../WorkflowCore.Persistence.MongoDB.csproj | 6 +- ...WorkflowCore.Persistence.PostgreSQL.csproj | 6 +- .../WorkflowCore.Persistence.SqlServer.csproj | 6 +- .../WorkflowCore.Persistence.Sqlite.csproj | 6 +- .../Services/AzureLockManager.cs | 2 +- .../Services/AzureStorageQueueProvider.cs | 7 +- .../WorkflowCore.Providers.Azure.csproj | 6 +- .../Services/RabbitMQProvider.cs | 5 +- ...orkflowCore.QueueProviders.RabbitMQ.csproj | 4 +- .../Services/ZeroMQProvider.cs | 5 +- .../WorkflowCore.QueueProviders.ZeroMQ.csproj | 6 +- src/samples/WorkflowCore.Sample03/Program.cs | 12 +- .../WorkflowCore.Sample03/Steps/AddNumbers.cs | 4 + test/ScratchPad/Program.cs | 7 +- .../DistributedLockProviderTests.cs | 13 +- .../LockProvider/AcquireLock.cs | 11 +- .../LockProvider/ReleaseLock.cs | 5 +- .../LockProvider/ReleaseLock_Exclusive.cs | 5 +- .../QueueProvider/MessageDistribution.cs | 7 +- .../QueueProvider/MultiMessageDistribution.cs | 7 +- 43 files changed, 606 insertions(+), 501 deletions(-) delete mode 100644 src/WorkflowCore/Services/EventTask.cs create mode 100644 src/WorkflowCore/Services/EventTaskDispatcher.cs create mode 100644 src/WorkflowCore/Services/QueueTaskDispatcher.cs delete mode 100644 src/WorkflowCore/Services/WorkflowTask.cs create mode 100644 src/WorkflowCore/Services/WorkflowTaskDispatcher.cs diff --git a/src/WorkflowCore/Interface/IDistributedLockProvider.cs b/src/WorkflowCore/Interface/IDistributedLockProvider.cs index ed3ec4c89..b280a9c9f 100644 --- a/src/WorkflowCore/Interface/IDistributedLockProvider.cs +++ b/src/WorkflowCore/Interface/IDistributedLockProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace WorkflowCore.Interface @@ -11,7 +12,7 @@ namespace WorkflowCore.Interface /// public interface IDistributedLockProvider { - Task AcquireLock(string Id); + Task AcquireLock(string Id, CancellationToken cancellationToken); Task ReleaseLock(string Id); diff --git a/src/WorkflowCore/Interface/IQueueProvider.cs b/src/WorkflowCore/Interface/IQueueProvider.cs index ae324a0b2..8c0238ed8 100644 --- a/src/WorkflowCore/Interface/IQueueProvider.cs +++ b/src/WorkflowCore/Interface/IQueueProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using WorkflowCore.Models; @@ -25,7 +26,9 @@ public interface IQueueProvider : IDisposable /// If the queue is empty, NULL is returned /// /// - Task DequeueWork(QueueType queue); + Task DequeueWork(QueueType queue, CancellationToken cancellationToken); + + bool IsDequeueBlocking { get; } Task Start(); Task Stop(); diff --git a/src/WorkflowCore/Models/WorkflowOptions.cs b/src/WorkflowCore/Models/WorkflowOptions.cs index ca6cd14ee..5c34aee70 100644 --- a/src/WorkflowCore/Models/WorkflowOptions.cs +++ b/src/WorkflowCore/Models/WorkflowOptions.cs @@ -20,7 +20,7 @@ public WorkflowOptions() { //set defaults PollInterval = TimeSpan.FromSeconds(10); - IdleTime = TimeSpan.FromMilliseconds(500); + IdleTime = TimeSpan.FromMilliseconds(100); ErrorRetryInterval = TimeSpan.FromSeconds(60); QueueFactory = new Func(sp => new SingleNodeQueueProvider()); diff --git a/src/WorkflowCore/Models/WorkflowStep.cs b/src/WorkflowCore/Models/WorkflowStep.cs index da55a257e..251a2e067 100644 --- a/src/WorkflowCore/Models/WorkflowStep.cs +++ b/src/WorkflowCore/Models/WorkflowStep.cs @@ -65,7 +65,6 @@ public virtual IStepBody ConstructBody(IServiceProvider serviceProvider) } return body; } - } public enum ExecutionPipelineDirective { Next = 0, Defer = 1, EndWorkflow = 2 } diff --git a/src/WorkflowCore/ServiceCollectionExtensions.cs b/src/WorkflowCore/ServiceCollectionExtensions.cs index 9a71d11e6..5bdc3c6c5 100644 --- a/src/WorkflowCore/ServiceCollectionExtensions.cs +++ b/src/WorkflowCore/ServiceCollectionExtensions.cs @@ -24,8 +24,8 @@ public static void AddWorkflow(this IServiceCollection services, Action(); services.AddSingleton(options); - services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddSingleton(); diff --git a/src/WorkflowCore/Services/EventTask.cs b/src/WorkflowCore/Services/EventTask.cs deleted file mode 100644 index 8e8579502..000000000 --- a/src/WorkflowCore/Services/EventTask.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using WorkflowCore.Interface; -using WorkflowCore.Models; - -namespace WorkflowCore.Services -{ - class EventTask : IBackgroundTask - { - private readonly IPersistenceProvider _persistenceStore; - private readonly IDistributedLockProvider _lockProvider; - private readonly IQueueProvider _queueProvider; - private readonly ILogger _logger; - private readonly WorkflowOptions _options; - private readonly Task _task; - private readonly IDateTimeProvider _datetimeProvider; - private bool _shutdown = true; - - public EventTask(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, WorkflowOptions options, IDateTimeProvider datetimeProvider) - { - _persistenceStore = persistenceStore; - _queueProvider = queueProvider; - _options = options; - _logger = loggerFactory.CreateLogger(); - _lockProvider = lockProvider; - _datetimeProvider = datetimeProvider; - _task = new Task(RunEvents); - } - - public void Start() - { - _shutdown = false; - _task.Start(); - } - - public void Stop() - { - _shutdown = true; - _task.Wait(); - } - - private async void RunEvents() - { - while (!_shutdown) - { - try - { - var eventId = await _queueProvider.DequeueWork(QueueType.Event); - if (eventId != null) - Parallel.Invoke(() => ProcessEvent(eventId)); - else - await Task.Delay(_options.IdleTime); //no work - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - } - } - } - - private async void ProcessEvent(string eventId) - { - try - { - if (await _lockProvider.AcquireLock($"evt:{eventId}")) - { - try - { - var evt = await _persistenceStore.GetEvent(eventId); - if (evt.EventTime <= _datetimeProvider.Now.ToUniversalTime()) - { - var subs = await _persistenceStore.GetSubcriptions(evt.EventName, evt.EventKey, evt.EventTime); - var success = true; - - foreach (var sub in subs.ToList()) - success = success && await SeedSubscription(evt, sub); - - if (success) - await _persistenceStore.MarkEventProcessed(eventId); - } - } - finally - { - await _lockProvider.ReleaseLock($"evt:{eventId}"); - } - } - else - { - _logger.LogInformation($"Event locked {eventId}"); - } - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - } - } - - private async Task SeedSubscription(Event evt, EventSubscription sub) - { - if (await _lockProvider.AcquireLock(sub.WorkflowId)) - { - try - { - var workflow = await _persistenceStore.GetWorkflowInstance(sub.WorkflowId); - var pointers = workflow.ExecutionPointers.Where(p => p.EventName == sub.EventName && p.EventKey == sub.EventKey && !p.EventPublished); - foreach (var p in pointers) - { - p.EventData = evt.EventData; - p.EventPublished = true; - p.Active = true; - } - workflow.NextExecution = 0; - await _persistenceStore.PersistWorkflow(workflow); - await _persistenceStore.TerminateSubscription(sub.Id); - return true; - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - return false; - } - finally - { - await _lockProvider.ReleaseLock(sub.WorkflowId); - await _queueProvider.QueueWork(sub.WorkflowId, QueueType.Workflow); - } - } - else - { - _logger.LogInformation("Workflow locked {0}", sub.WorkflowId); - return false; - } - } - } -} diff --git a/src/WorkflowCore/Services/EventTaskDispatcher.cs b/src/WorkflowCore/Services/EventTaskDispatcher.cs new file mode 100644 index 000000000..01ad01856 --- /dev/null +++ b/src/WorkflowCore/Services/EventTaskDispatcher.cs @@ -0,0 +1,97 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using WorkflowCore.Interface; +using WorkflowCore.Models; + +namespace WorkflowCore.Services +{ + class EventTaskDispatcher : QueueTaskDispatcher, IBackgroundTask + { + private readonly IPersistenceProvider _persistenceStore; + private readonly IDistributedLockProvider _lockProvider; + private readonly IDateTimeProvider _datetimeProvider; + + protected override QueueType Queue => QueueType.Event; + + public EventTaskDispatcher(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, WorkflowOptions options, IDateTimeProvider datetimeProvider) + : base(queueProvider, loggerFactory, options) + { + _persistenceStore = persistenceStore; + _lockProvider = lockProvider; + _datetimeProvider = datetimeProvider; + } + + protected override async Task ProcessItem(string itemId, CancellationToken cancellationToken) + { + if (await _lockProvider.AcquireLock($"evt:{itemId}", cancellationToken)) + { + try + { + cancellationToken.ThrowIfCancellationRequested(); + var evt = await _persistenceStore.GetEvent(itemId); + if (evt.EventTime <= _datetimeProvider.Now.ToUniversalTime()) + { + var subs = await _persistenceStore.GetSubcriptions(evt.EventName, evt.EventKey, evt.EventTime); + var success = true; + + foreach (var sub in subs.ToList()) + success = success && await SeedSubscription(evt, sub, cancellationToken); + + if (success) + await _persistenceStore.MarkEventProcessed(itemId); + } + } + finally + { + await _lockProvider.ReleaseLock($"evt:{itemId}"); + } + } + else + { + Logger.LogInformation($"Event locked {itemId}"); + } + } + + private async Task SeedSubscription(Event evt, EventSubscription sub, CancellationToken cancellationToken) + { + if (await _lockProvider.AcquireLock(sub.WorkflowId, cancellationToken)) + { + try + { + var workflow = await _persistenceStore.GetWorkflowInstance(sub.WorkflowId); + var pointers = workflow.ExecutionPointers.Where(p => p.EventName == sub.EventName && p.EventKey == sub.EventKey && !p.EventPublished); + foreach (var p in pointers) + { + p.EventData = evt.EventData; + p.EventPublished = true; + p.Active = true; + } + workflow.NextExecution = 0; + await _persistenceStore.PersistWorkflow(workflow); + await _persistenceStore.TerminateSubscription(sub.Id); + return true; + } + catch (Exception ex) + { + Logger.LogError(ex.Message); + return false; + } + finally + { + await _lockProvider.ReleaseLock(sub.WorkflowId); + await QueueProvider.QueueWork(sub.WorkflowId, QueueType.Workflow); + } + } + else + { + Logger.LogInformation("Workflow locked {0}", sub.WorkflowId); + return false; + } + } + } +} diff --git a/src/WorkflowCore/Services/QueueTaskDispatcher.cs b/src/WorkflowCore/Services/QueueTaskDispatcher.cs new file mode 100644 index 000000000..6cfb6a027 --- /dev/null +++ b/src/WorkflowCore/Services/QueueTaskDispatcher.cs @@ -0,0 +1,114 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using WorkflowCore.Interface; +using WorkflowCore.Models; + +namespace WorkflowCore.Services +{ + public abstract class QueueTaskDispatcher : IBackgroundTask + { + protected abstract QueueType Queue { get; } + protected virtual int MaxConcurrentItems => Math.Max(Environment.ProcessorCount, 2); + + protected readonly IQueueProvider QueueProvider; + protected readonly ILogger Logger; + protected readonly WorkflowOptions Options; + protected Task DispatchTask; + private SemaphoreSlim _semaphore; + private CancellationTokenSource _cancellationTokenSource; + + protected QueueTaskDispatcher(IQueueProvider queueProvider, ILoggerFactory loggerFactory, WorkflowOptions options) + { + QueueProvider = queueProvider; + Options = options; + Logger = loggerFactory.CreateLogger(GetType()); + } + + protected abstract Task ProcessItem(string itemId, CancellationToken cancellationToken); + + public virtual void Start() + { + if (DispatchTask != null) + throw new InvalidOperationException(); + + _cancellationTokenSource = new CancellationTokenSource(); + _semaphore = new SemaphoreSlim(MaxConcurrentItems); + DispatchTask = new Task(Execute); + DispatchTask.Start(); + } + + public virtual void Stop() + { + _cancellationTokenSource.Cancel(); + DispatchTask.Wait(); + + for (var i = 0; i < MaxConcurrentItems; i++) + _semaphore.Wait(); + + DispatchTask = null; + } + + private async void Execute() + { + var cancelToken = _cancellationTokenSource.Token; + while (!cancelToken.IsCancellationRequested) + { + try + { + await _semaphore.WaitAsync(cancelToken); + string item; + try + { + item = await QueueProvider.DequeueWork(Queue, cancelToken); + } + catch + { + _semaphore.Release(); + throw; + } + + if (item == null) + { + _semaphore.Release(); + if (!QueueProvider.IsDequeueBlocking) + await Task.Delay(Options.IdleTime, cancelToken); + continue; + } + + new Task(() => ExecuteItem(item, cancelToken)).Start(); + } + catch (OperationCanceledException) + { + } + catch (Exception ex) + { + Logger.LogError(ex.Message); + } + } + } + + private async void ExecuteItem(string itemId, CancellationToken cancellationToken) + { + try + { + await ProcessItem(itemId, cancellationToken); + } + catch (OperationCanceledException) + { + Logger.LogInformation($"Operation cancelled while processing {itemId}"); + } + catch (Exception ex) + { + Logger.LogError($"Error executing item {itemId} - {ex.Message}"); + } + finally + { + _semaphore.Release(); + } + } + } +} diff --git a/src/WorkflowCore/Services/RunnablePoller.cs b/src/WorkflowCore/Services/RunnablePoller.cs index 320655b3b..ecb77e3ad 100644 --- a/src/WorkflowCore/Services/RunnablePoller.cs +++ b/src/WorkflowCore/Services/RunnablePoller.cs @@ -49,7 +49,7 @@ private async void PollRunnables(object target) { try { - if (await _lockProvider.AcquireLock("poll runnables")) + if (await _lockProvider.AcquireLock("poll runnables", new CancellationToken())) { try { @@ -74,7 +74,7 @@ private async void PollRunnables(object target) try { - if (await _lockProvider.AcquireLock("unprocessed events")) + if (await _lockProvider.AcquireLock("unprocessed events", new CancellationToken())) { try { diff --git a/src/WorkflowCore/Services/SingleNodeLockProvider.cs b/src/WorkflowCore/Services/SingleNodeLockProvider.cs index c56e153a1..8a6534954 100644 --- a/src/WorkflowCore/Services/SingleNodeLockProvider.cs +++ b/src/WorkflowCore/Services/SingleNodeLockProvider.cs @@ -17,7 +17,7 @@ public class SingleNodeLockProvider : IDistributedLockProvider { private List _locks = new List(); - public async Task AcquireLock(string Id) + public async Task AcquireLock(string Id, CancellationToken cancellationToken) { lock (_locks) { diff --git a/src/WorkflowCore/Services/SingleNodeQueueProvider.cs b/src/WorkflowCore/Services/SingleNodeQueueProvider.cs index 384fa48ef..4c91fdf7e 100644 --- a/src/WorkflowCore/Services/SingleNodeQueueProvider.cs +++ b/src/WorkflowCore/Services/SingleNodeQueueProvider.cs @@ -16,22 +16,24 @@ namespace WorkflowCore.Services public class SingleNodeQueueProvider : IQueueProvider { - private ConcurrentQueue _runQueue = new ConcurrentQueue(); - private ConcurrentQueue _eventQueue = new ConcurrentQueue(); + private readonly BlockingCollection _runQueue = new BlockingCollection(); + private readonly BlockingCollection _eventQueue = new BlockingCollection(); + + public bool IsDequeueBlocking => true; public async Task QueueWork(string id, QueueType queue) { - SelectQueue(queue).Enqueue(id); + SelectQueue(queue).Add(id); } - public async Task DequeueWork(QueueType queue) + public async Task DequeueWork(QueueType queue, CancellationToken cancellationToken) { - if (SelectQueue(queue).TryDequeue(out string id)) + if (SelectQueue(queue).TryTake(out string id, 100, cancellationToken)) return id; return null; } - + public async Task Start() { } @@ -44,7 +46,7 @@ public void Dispose() { } - private ConcurrentQueue SelectQueue(QueueType queue) + private BlockingCollection SelectQueue(QueueType queue) { switch (queue) { diff --git a/src/WorkflowCore/Services/WorkflowHost.cs b/src/WorkflowCore/Services/WorkflowHost.cs index 4fcffb3a9..9c9a51b2e 100644 --- a/src/WorkflowCore/Services/WorkflowHost.cs +++ b/src/WorkflowCore/Services/WorkflowHost.cs @@ -156,7 +156,7 @@ public void RegisterWorkflow() public async Task SuspendWorkflow(string workflowId) { - if (await LockProvider.AcquireLock(workflowId)) + if (await LockProvider.AcquireLock(workflowId, new CancellationToken())) { try { @@ -179,7 +179,7 @@ public async Task SuspendWorkflow(string workflowId) public async Task ResumeWorkflow(string workflowId) { - if (await LockProvider.AcquireLock(workflowId)) + if (await LockProvider.AcquireLock(workflowId, new CancellationToken())) { bool requeue = false; try @@ -206,7 +206,7 @@ public async Task ResumeWorkflow(string workflowId) public async Task TerminateWorkflow(string workflowId) { - if (await LockProvider.AcquireLock(workflowId)) + if (await LockProvider.AcquireLock(workflowId, new CancellationToken())) { try { diff --git a/src/WorkflowCore/Services/WorkflowTask.cs b/src/WorkflowCore/Services/WorkflowTask.cs deleted file mode 100644 index 7255a1df1..000000000 --- a/src/WorkflowCore/Services/WorkflowTask.cs +++ /dev/null @@ -1,155 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using WorkflowCore.Interface; -using WorkflowCore.Models; - -namespace WorkflowCore.Services -{ - class WorkflowTask : IBackgroundTask - { - private readonly IPersistenceProvider _persistenceStore; - private readonly IDistributedLockProvider _lockProvider; - private readonly IWorkflowExecutor _executor; - private readonly IQueueProvider _queueProvider; - private readonly ILogger _logger; - private readonly IList _tasks; - private readonly WorkflowOptions _options; - private readonly IDateTimeProvider _datetimeProvider; - private bool _shutdown = true; - - public WorkflowTask(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, IWorkflowExecutor executor, IDateTimeProvider datetimeProvider, WorkflowOptions options) - { - _persistenceStore = persistenceStore; - _queueProvider = queueProvider; - _executor = executor; - _options = options; - _logger = loggerFactory.CreateLogger(); - _lockProvider = lockProvider; - - _tasks = new List(); - for (int i = 0; i < Environment.ProcessorCount; i++) - _tasks.Add(new Task(RunWorkflows)); - - _datetimeProvider = datetimeProvider; - persistenceStore.EnsureStoreExists(); - } - - public void Start() - { - _shutdown = false; - foreach (var task in _tasks) - task.Start(); - } - - public void Stop() - { - _shutdown = true; - foreach (var task in _tasks) - task.Wait(); - } - - /// - /// Worker task body - /// - private async void RunWorkflows() - { - while (!_shutdown) - { - try - { - var workflowId = await _queueProvider.DequeueWork(QueueType.Workflow); - - if (workflowId != null) - await ProcessWorkflow(workflowId); - else - await Task.Delay(_options.IdleTime); //no work - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - } - } - } - - private async Task ProcessWorkflow(string workflowId) - { - try - { - if (await _lockProvider.AcquireLock(workflowId)) - { - WorkflowInstance workflow = null; - WorkflowExecutorResult result = null; - try - { - workflow = await _persistenceStore.GetWorkflowInstance(workflowId); - if (workflow.Status == WorkflowStatus.Runnable) - { - try - { - result = await _executor.Execute(workflow, _options); - } - finally - { - await _persistenceStore.PersistWorkflow(workflow); - } - } - } - finally - { - await _lockProvider.ReleaseLock(workflowId); - if ((workflow != null) && (result != null)) - { - foreach (var sub in result.Subscriptions) - await SubscribeEvent(sub); - - await _persistenceStore.PersistErrors(result.Errors); - - var readAheadTicks = _datetimeProvider.Now.Add(_options.PollInterval).ToUniversalTime().Ticks; - - if ((workflow.Status == WorkflowStatus.Runnable) && workflow.NextExecution.HasValue && workflow.NextExecution.Value < readAheadTicks) - { - Parallel.Invoke(async () => - { - if (!workflow.NextExecution.HasValue) - return; - - var target = (workflow.NextExecution.Value - _datetimeProvider.Now.ToUniversalTime().Ticks); - if (target > 0) - await Task.Delay(TimeSpan.FromTicks(target)); - - await _queueProvider.QueueWork(workflowId, QueueType.Workflow); - }); - } - } - } - } - else - { - _logger.LogInformation("Workflow locked {0}", workflowId); - } - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - } - } - - private async Task SubscribeEvent(EventSubscription subscription) - { - //TODO: move to own class - _logger.LogDebug("Subscribing to event {0} {1} for workflow {2} step {3}", subscription.EventName, subscription.EventKey, subscription.WorkflowId, subscription.StepId); - - await _persistenceStore.CreateEventSubscription(subscription); - var events = await _persistenceStore.GetEvents(subscription.EventName, subscription.EventKey, subscription.SubscribeAsOf); - foreach (var evt in events) - { - await _persistenceStore.MarkEventUnprocessed(evt); - await _queueProvider.QueueWork(evt, QueueType.Event); - } - } - } -} diff --git a/src/WorkflowCore/Services/WorkflowTaskDispatcher.cs b/src/WorkflowCore/Services/WorkflowTaskDispatcher.cs new file mode 100644 index 000000000..7f5ad82b0 --- /dev/null +++ b/src/WorkflowCore/Services/WorkflowTaskDispatcher.cs @@ -0,0 +1,111 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using WorkflowCore.Interface; +using WorkflowCore.Models; + +namespace WorkflowCore.Services +{ + class WorkflowTaskDispatcher : QueueTaskDispatcher, IBackgroundTask + { + private readonly IPersistenceProvider _persistenceStore; + private readonly IDistributedLockProvider _lockProvider; + private readonly IWorkflowExecutor _executor; + private readonly IDateTimeProvider _datetimeProvider; + + protected override QueueType Queue => QueueType.Workflow; + + public WorkflowTaskDispatcher(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, IWorkflowExecutor executor, IDateTimeProvider datetimeProvider, WorkflowOptions options) + : base(queueProvider, loggerFactory, options) + { + _persistenceStore = persistenceStore; + _executor = executor; + _lockProvider = lockProvider; + _datetimeProvider = datetimeProvider; + persistenceStore.EnsureStoreExists(); + } + + protected override async Task ProcessItem(string itemId, CancellationToken cancellationToken) + { + if (await _lockProvider.AcquireLock(itemId, cancellationToken)) + { + WorkflowInstance workflow = null; + WorkflowExecutorResult result = null; + try + { + cancellationToken.ThrowIfCancellationRequested(); + workflow = await _persistenceStore.GetWorkflowInstance(itemId); + if (workflow.Status == WorkflowStatus.Runnable) + { + try + { + result = await _executor.Execute(workflow, Options); + } + finally + { + await _persistenceStore.PersistWorkflow(workflow); + } + } + } + finally + { + await _lockProvider.ReleaseLock(itemId); + if ((workflow != null) && (result != null)) + { + foreach (var sub in result.Subscriptions) + await SubscribeEvent(sub); + + await _persistenceStore.PersistErrors(result.Errors); + + var readAheadTicks = _datetimeProvider.Now.Add(Options.PollInterval).ToUniversalTime().Ticks; + + if ((workflow.Status == WorkflowStatus.Runnable) && workflow.NextExecution.HasValue && workflow.NextExecution.Value < readAheadTicks) + { + new Task(() => FutureQueue(workflow, cancellationToken)).Start(); + } + } + } + } + else + { + Logger.LogInformation("Workflow locked {0}", itemId); + } + } + + private async Task SubscribeEvent(EventSubscription subscription) + { + //TODO: move to own class + Logger.LogDebug("Subscribing to event {0} {1} for workflow {2} step {3}", subscription.EventName, subscription.EventKey, subscription.WorkflowId, subscription.StepId); + + await _persistenceStore.CreateEventSubscription(subscription); + var events = await _persistenceStore.GetEvents(subscription.EventName, subscription.EventKey, subscription.SubscribeAsOf); + foreach (var evt in events) + { + await _persistenceStore.MarkEventUnprocessed(evt); + await QueueProvider.QueueWork(evt, QueueType.Event); + } + } + + private async void FutureQueue(WorkflowInstance workflow, CancellationToken cancellationToken) + { + try + { + if (!workflow.NextExecution.HasValue) + return; + + var target = (workflow.NextExecution.Value - _datetimeProvider.Now.ToUniversalTime().Ticks); + if (target > 0) + await Task.Delay(TimeSpan.FromTicks(target), cancellationToken); + + await QueueProvider.QueueWork(workflow.Id, QueueType.Workflow); + } + catch (Exception ex) + { + Logger.LogError(ex.Message); + } + } + } +} diff --git a/src/providers/WorkflowCore.LockProviders.Redlock/Services/RedlockProvider.cs b/src/providers/WorkflowCore.LockProviders.Redlock/Services/RedlockProvider.cs index 971a6ac9f..566ab6d9c 100644 --- a/src/providers/WorkflowCore.LockProviders.Redlock/Services/RedlockProvider.cs +++ b/src/providers/WorkflowCore.LockProviders.Redlock/Services/RedlockProvider.cs @@ -36,7 +36,7 @@ public RedlockProvider(params IConnectionMultiplexer[] list) this.redisMasterDictionary.Add(item.GetEndPoints().First().ToString(), item); } - public async Task AcquireLock(string Id) + public async Task AcquireLock(string Id, CancellationToken cancellationToken) { Lock lockObject = null; if (Lock(Id, TimeSpan.FromMinutes(30), out lockObject)) diff --git a/src/providers/WorkflowCore.LockProviders.Redlock/WorkflowCore.LockProviders.Redlock.csproj b/src/providers/WorkflowCore.LockProviders.Redlock/WorkflowCore.LockProviders.Redlock.csproj index 9bd364284..e51b242ea 100644 --- a/src/providers/WorkflowCore.LockProviders.Redlock/WorkflowCore.LockProviders.Redlock.csproj +++ b/src/providers/WorkflowCore.LockProviders.Redlock/WorkflowCore.LockProviders.Redlock.csproj @@ -17,9 +17,11 @@ false false false - 1.2.7 + 1.3.0 Distributed lock provider for Workflow-core using Redis + 1.3.0.0 + 1.3.0.0 diff --git a/src/providers/WorkflowCore.LockProviders.SqlServer/SqlLockProvider.cs b/src/providers/WorkflowCore.LockProviders.SqlServer/SqlLockProvider.cs index 137068dbc..7726d3ebf 100644 --- a/src/providers/WorkflowCore.LockProviders.SqlServer/SqlLockProvider.cs +++ b/src/providers/WorkflowCore.LockProviders.SqlServer/SqlLockProvider.cs @@ -30,14 +30,14 @@ public SqlLockProvider(string connectionString, ILoggerFactory logFactory) } - public async Task AcquireLock(string Id) + public async Task AcquireLock(string Id, CancellationToken cancellationToken) { if (_mutex.WaitOne()) { try { var connection = new SqlConnection(_connectionString); - await connection.OpenAsync(); + await connection.OpenAsync(cancellationToken); try { var cmd = connection.CreateCommand(); @@ -49,7 +49,7 @@ public async Task AcquireLock(string Id) cmd.Parameters.AddWithValue("@LockTimeout", 0); var returnParameter = cmd.Parameters.Add("RetVal", SqlDbType.Int); returnParameter.Direction = ParameterDirection.ReturnValue; - await cmd.ExecuteNonQueryAsync(); + await cmd.ExecuteNonQueryAsync(cancellationToken); var result = Convert.ToInt32(returnParameter.Value); switch (result) diff --git a/src/providers/WorkflowCore.LockProviders.SqlServer/WorkflowCore.LockProviders.SqlServer.csproj b/src/providers/WorkflowCore.LockProviders.SqlServer/WorkflowCore.LockProviders.SqlServer.csproj index 6eb110b2d..e6365bc1b 100644 --- a/src/providers/WorkflowCore.LockProviders.SqlServer/WorkflowCore.LockProviders.SqlServer.csproj +++ b/src/providers/WorkflowCore.LockProviders.SqlServer/WorkflowCore.LockProviders.SqlServer.csproj @@ -5,6 +5,7 @@ Distributed lock provider for Workflow-core using SQL Server https://github.com/danielgerlag/workflow-core https://github.com/danielgerlag/workflow-core/blob/master/LICENSE.md + 1.3.0 diff --git a/src/providers/WorkflowCore.LockProviders.ZeroMQ/Services/ZeroMQLockProvider.cs b/src/providers/WorkflowCore.LockProviders.ZeroMQ/Services/ZeroMQLockProvider.cs index f222855f9..738492b1b 100644 --- a/src/providers/WorkflowCore.LockProviders.ZeroMQ/Services/ZeroMQLockProvider.cs +++ b/src/providers/WorkflowCore.LockProviders.ZeroMQ/Services/ZeroMQLockProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; +using System.Threading; using System.Threading.Tasks; using WorkflowCore.Interface; using WorkflowCore.LockProviders.ZeroMQ.Models; @@ -41,7 +42,7 @@ public ZeroMQLockProvider(int port, IEnumerable peers, ILoggerFactory lo _server.ReceiveReady += Server_ReceiveReady; } - public async Task AcquireLock(string Id) + public async Task AcquireLock(string Id, CancellationToken cancellationToken) { if (_lockRegistry.Any(x => x.ResourceId == Id)) return false; diff --git a/src/providers/WorkflowCore.LockProviders.ZeroMQ/WorkflowCore.LockProviders.ZeroMQ.csproj b/src/providers/WorkflowCore.LockProviders.ZeroMQ/WorkflowCore.LockProviders.ZeroMQ.csproj index ca8756e8b..d59a931f1 100644 --- a/src/providers/WorkflowCore.LockProviders.ZeroMQ/WorkflowCore.LockProviders.ZeroMQ.csproj +++ b/src/providers/WorkflowCore.LockProviders.ZeroMQ/WorkflowCore.LockProviders.ZeroMQ.csproj @@ -17,9 +17,9 @@ false false false - 1.2.7 - 1.2.7.0 - 1.2.7.0 + 1.3.0 + 1.3.0.0 + 1.3.0.0 diff --git a/src/providers/WorkflowCore.Persistence.EntityFramework/Services/EntityFrameworkPersistenceProvider.cs b/src/providers/WorkflowCore.Persistence.EntityFramework/Services/EntityFrameworkPersistenceProvider.cs index 18e10c739..14961d7b7 100644 --- a/src/providers/WorkflowCore.Persistence.EntityFramework/Services/EntityFrameworkPersistenceProvider.cs +++ b/src/providers/WorkflowCore.Persistence.EntityFramework/Services/EntityFrameworkPersistenceProvider.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using WorkflowCore.Interface; using WorkflowCore.Persistence.EntityFramework.Models; @@ -10,13 +11,13 @@ namespace WorkflowCore.Persistence.EntityFramework.Services { -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public abstract class EntityFrameworkPersistenceProvider : DbContext, IPersistenceProvider { protected readonly bool _canCreateDB; protected readonly bool _canMigrateDB; + private readonly AutoResetEvent _mutex = new AutoResetEvent(true); - public EntityFrameworkPersistenceProvider(bool canCreateDB, bool canMigrateDB) + protected EntityFrameworkPersistenceProvider(bool canCreateDB, bool canMigrateDB) { _canCreateDB = canCreateDB; _canMigrateDB = canMigrateDB; @@ -68,56 +69,67 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) public async Task CreateEventSubscription(EventSubscription subscription) { - lock (this) + _mutex.WaitOne(); + try { subscription.Id = Guid.NewGuid().ToString(); var persistable = subscription.ToPersistable(); var result = Set().Add(persistable); - SaveChanges(); + await SaveChangesAsync(); Entry(persistable).State = EntityState.Detached; return subscription.Id; } + finally + { + _mutex.Set(); + } } public async Task CreateNewWorkflow(WorkflowInstance workflow) { - lock (this) + _mutex.WaitOne(); + try { workflow.Id = Guid.NewGuid().ToString(); var persistable = workflow.ToPersistable(); var result = Set().Add(persistable); - SaveChanges(); + await SaveChangesAsync(); Entry(persistable).State = EntityState.Detached; return workflow.Id; } + finally + { + _mutex.Set(); + } } public async Task> GetRunnableInstances() { - lock (this) + _mutex.WaitOne(); + try { var now = DateTime.Now.ToUniversalTime().Ticks; - var raw = Set() + var raw = await Set() .Where(x => x.NextExecution.HasValue && (x.NextExecution <= now) && (x.Status == WorkflowStatus.Runnable)) .Select(x => x.InstanceId) - .ToList(); - - List result = new List(); - - foreach (var s in raw) - result.Add(s.ToString()); + .ToListAsync(); - return result; + return raw.Select(s => s.ToString()).ToList(); + } + finally + { + _mutex.Set(); } } public async Task> GetWorkflowInstances(WorkflowStatus? status, string type, DateTime? createdFrom, DateTime? createdTo, int skip, int take) { - lock (this) + _mutex.WaitOne(); + try { IQueryable query = Set() .Include(wf => wf.ExecutionPointers) - .ThenInclude(ep => ep.ExtensionAttributes) + .ThenInclude(ep => ep.ExtensionAttributes) .Include(wf => wf.ExecutionPointers) .AsQueryable(); @@ -133,7 +145,7 @@ public async Task> GetWorkflowInstances(WorkflowSt if (createdTo.HasValue) query = query.Where(x => x.CreateTime <= createdTo.Value); - var rawResult = query.Skip(skip).Take(take).ToList(); + var rawResult = await query.Skip(skip).Take(take).ToListAsync(); List result = new List(); foreach (var item in rawResult) @@ -141,41 +153,51 @@ public async Task> GetWorkflowInstances(WorkflowSt return result; } + finally + { + _mutex.Set(); + } } public async Task GetWorkflowInstance(string Id) { - lock (this) + _mutex.WaitOne(); + try { - Guid uid = new Guid(Id); - var raw = Set() + var uid = new Guid(Id); + var raw = await Set() .Include(wf => wf.ExecutionPointers) - .ThenInclude(ep => ep.ExtensionAttributes) + .ThenInclude(ep => ep.ExtensionAttributes) .Include(wf => wf.ExecutionPointers) - .First(x => x.InstanceId == uid); + .FirstAsync(x => x.InstanceId == uid); if (raw == null) return null; return raw.ToWorkflowInstance(); } + finally + { + _mutex.Set(); + } } public async Task PersistWorkflow(WorkflowInstance workflow) { - lock (this) + _mutex.WaitOne(); + try { - Guid uid = new Guid(workflow.Id); - var existingEntity = Set() + var uid = new Guid(workflow.Id); + var existingEntity = await Set() .Where(x => x.InstanceId == uid) .Include(wf => wf.ExecutionPointers) - .ThenInclude(ep => ep.ExtensionAttributes) + .ThenInclude(ep => ep.ExtensionAttributes) .Include(wf => wf.ExecutionPointers) .AsTracking() - .First(); + .FirstAsync(); var persistable = workflow.ToPersistable(existingEntity); - SaveChanges(); + await SaveChangesAsync(); Entry(persistable).State = EntityState.Detached; foreach (var ep in persistable.ExecutionPointers) { @@ -183,19 +205,28 @@ public async Task PersistWorkflow(WorkflowInstance workflow) foreach (var attr in ep.ExtensionAttributes) Entry(attr).State = EntityState.Detached; - + } } + finally + { + _mutex.Set(); + } } public async Task TerminateSubscription(string eventSubscriptionId) { - lock (this) + _mutex.WaitOne(); + try { - Guid uid = new Guid(eventSubscriptionId); - var existing = Set().First(x => x.SubscriptionId == uid); + var uid = new Guid(eventSubscriptionId); + var existing = await Set().FirstAsync(x => x.SubscriptionId == uid); Set().Remove(existing); - SaveChanges(); + await SaveChangesAsync(); + } + finally + { + _mutex.Set(); } } @@ -216,132 +247,165 @@ public virtual void EnsureStoreExists() public async Task> GetSubcriptions(string eventName, string eventKey, DateTime asOf) { - lock (this) + _mutex.WaitOne(); + try { - var raw = Set().Where(x => x.EventName == eventName && x.EventKey == eventKey && x.SubscribeAsOf <= asOf).ToList(); - - List result = new List(); - foreach (var item in raw) - result.Add(item.ToEventSubscription()); + var raw = await Set() + .Where(x => x.EventName == eventName && x.EventKey == eventKey && x.SubscribeAsOf <= asOf) + .ToListAsync(); - return result; + return raw.Select(item => item.ToEventSubscription()).ToList(); + } + finally + { + _mutex.Set(); } } public async Task CreateEvent(Event newEvent) { - lock (this) + _mutex.WaitOne(); + try { newEvent.Id = Guid.NewGuid().ToString(); var persistable = newEvent.ToPersistable(); var result = Set().Add(persistable); - SaveChanges(); + await SaveChangesAsync(); Entry(persistable).State = EntityState.Detached; return newEvent.Id; } + finally + { + _mutex.Set(); + } } public async Task GetEvent(string id) { - lock (this) + _mutex.WaitOne(); + try { Guid uid = new Guid(id); - var raw = Set() - .First(x => x.EventId == uid); + var raw = await Set() + .FirstAsync(x => x.EventId == uid); if (raw == null) return null; return raw.ToEvent(); } + finally + { + _mutex.Set(); + } } public async Task> GetRunnableEvents() { var now = DateTime.Now.ToUniversalTime(); - - lock (this) + _mutex.WaitOne(); + try { - var raw = Set() + var raw = await Set() .Where(x => !x.IsProcessed) .Where(x => x.EventTime <= now) .Select(x => x.EventId) - .ToList(); - - List result = new List(); + .ToListAsync(); - foreach (var s in raw) - result.Add(s.ToString()); - - return result; + return raw.Select(s => s.ToString()).ToList(); + } + finally + { + _mutex.Set(); } } public async Task MarkEventProcessed(string id) { - lock (this) + _mutex.WaitOne(); + try { - Guid uid = new Guid(id); - var existingEntity = Set() + var uid = new Guid(id); + var existingEntity = await Set() .Where(x => x.EventId == uid) .AsTracking() - .First(); + .FirstAsync(); existingEntity.IsProcessed = true; - SaveChanges(); - Entry(existingEntity).State = EntityState.Detached; + await SaveChangesAsync(); + Entry(existingEntity).State = EntityState.Detached; + } + finally + { + _mutex.Set(); } } public async Task> GetEvents(string eventName, string eventKey, DateTime asOf) { - lock (this) + _mutex.WaitOne(); + try { - var raw = Set() + var raw = await Set() .Where(x => x.EventName == eventName && x.EventKey == eventKey) .Where(x => x.EventTime >= asOf) .Select(x => x.EventId) - .ToList(); + .ToListAsync(); - List result = new List(); + var result = new List(); foreach (var s in raw) result.Add(s.ToString()); return result; } + finally + { + _mutex.Set(); + } } public async Task MarkEventUnprocessed(string id) { - lock (this) + _mutex.WaitOne(); + try { - Guid uid = new Guid(id); - var existingEntity = Set() + var uid = new Guid(id); + var existingEntity = await Set() .Where(x => x.EventId == uid) .AsTracking() - .First(); + .FirstAsync(); existingEntity.IsProcessed = false; - SaveChanges(); + await SaveChangesAsync(); Entry(existingEntity).State = EntityState.Detached; } + finally + { + _mutex.Set(); + } } public async Task PersistErrors(IEnumerable errors) { - if (errors.Count() > 0) + _mutex.WaitOne(); + try { - lock (this) + var executionErrors = errors as ExecutionError[] ?? errors.ToArray(); + if (executionErrors.Any()) { - foreach (var error in errors) + foreach (var error in executionErrors) { Set().Add(error.ToPersistable()); } - SaveChanges(); + await SaveChangesAsync(); + } } + finally + { + _mutex.Set(); + } } } -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously } diff --git a/src/providers/WorkflowCore.Persistence.EntityFramework/WorkflowCore.Persistence.EntityFramework.csproj b/src/providers/WorkflowCore.Persistence.EntityFramework/WorkflowCore.Persistence.EntityFramework.csproj index 1e64aae68..0670758a6 100644 --- a/src/providers/WorkflowCore.Persistence.EntityFramework/WorkflowCore.Persistence.EntityFramework.csproj +++ b/src/providers/WorkflowCore.Persistence.EntityFramework/WorkflowCore.Persistence.EntityFramework.csproj @@ -17,8 +17,10 @@ false false false - 1.2.9 + 1.3.0 Base package for Workflow-core peristence providers using entity framework + 1.3.0.0 + 1.3.0.0 diff --git a/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs b/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs index 2670a49cd..4456249e1 100644 --- a/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs +++ b/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs @@ -13,7 +13,6 @@ namespace WorkflowCore.Persistence.MongoDB.Services { -#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously public class MongoPersistenceProvider : IPersistenceProvider { @@ -91,37 +90,13 @@ static void CreateIndexes(MongoPersistenceProvider instance) } } - private IMongoCollection WorkflowInstances - { - get - { - return _database.GetCollection("wfc.workflows"); - } - } + private IMongoCollection WorkflowInstances => _database.GetCollection("wfc.workflows"); - private IMongoCollection EventSubscriptions - { - get - { - return _database.GetCollection("wfc.subscriptions"); - } - } + private IMongoCollection EventSubscriptions => _database.GetCollection("wfc.subscriptions"); - private IMongoCollection Events - { - get - { - return _database.GetCollection("wfc.events"); - } - } + private IMongoCollection Events => _database.GetCollection("wfc.events"); - private IMongoCollection ExecutionErrors - { - get - { - return _database.GetCollection("wfc.execution_errors"); - } - } + private IMongoCollection ExecutionErrors => _database.GetCollection("wfc.execution_errors"); public async Task CreateNewWorkflow(WorkflowInstance workflow) { @@ -137,14 +112,17 @@ public async Task PersistWorkflow(WorkflowInstance workflow) public async Task> GetRunnableInstances() { var now = DateTime.Now.ToUniversalTime().Ticks; - return WorkflowInstances.AsQueryable() - .Where(x => x.NextExecution.HasValue && (x.NextExecution <= now) && (x.Status == WorkflowStatus.Runnable)) - .Select(x => x.Id).ToList(); + var query = WorkflowInstances + .Find(x => x.NextExecution.HasValue && (x.NextExecution <= now) && (x.Status == WorkflowStatus.Runnable)) + .Project(x => x.Id); + + return await query.ToListAsync(); } public async Task GetWorkflowInstance(string Id) { - return WorkflowInstances.AsQueryable().First(x => x.Id == Id); + var result = await WorkflowInstances.FindAsync(x => x.Id == Id); + return await result.FirstAsync(); } public async Task> GetWorkflowInstances(WorkflowStatus? status, string type, DateTime? createdFrom, DateTime? createdTo, int skip, int take) @@ -184,8 +162,10 @@ public void EnsureStoreExists() public async Task> GetSubcriptions(string eventName, string eventKey, DateTime asOf) { - return EventSubscriptions.AsQueryable() - .Where(x => x.EventName == eventName && x.EventKey == eventKey && x.SubscribeAsOf <= asOf).ToList(); + var query = EventSubscriptions + .Find(x => x.EventName == eventName && x.EventKey == eventKey && x.SubscribeAsOf <= asOf); + + return await query.ToListAsync(); } public async Task CreateEvent(Event newEvent) @@ -196,18 +176,18 @@ public async Task CreateEvent(Event newEvent) public async Task GetEvent(string id) { - return Events.AsQueryable().First(x => x.Id == id); + var result = await Events.FindAsync(x => x.Id == id); + return await result.FirstAsync(); } public async Task> GetRunnableEvents() { var now = DateTime.Now.ToUniversalTime(); + var query = Events + .Find(x => !x.IsProcessed && x.EventTime <= now) + .Project(x => x.Id); - return Events.AsQueryable() - .Where(x => !x.IsProcessed) - .Where(x => x.EventTime <= now) - .Select(x => x.Id) - .ToList(); + return await query.ToListAsync(); } public async Task MarkEventProcessed(string id) @@ -220,11 +200,11 @@ public async Task MarkEventProcessed(string id) public async Task> GetEvents(string eventName, string eventKey, DateTime asOf) { - return Events.AsQueryable() - .Where(x => x.EventName == eventName && x.EventKey == eventKey) - .Where(x => x.EventTime >= asOf) - .Select(x => x.Id) - .ToList(); + var query = Events + .Find(x => x.EventName == eventName && x.EventKey == eventKey && x.EventTime >= asOf) + .Project(x => x.Id); + + return await query.ToListAsync(); } public async Task MarkEventUnprocessed(string id) @@ -237,9 +217,8 @@ public async Task MarkEventUnprocessed(string id) public async Task PersistErrors(IEnumerable errors) { - if (errors.Count() > 0) + if (errors.Any()) await ExecutionErrors.InsertManyAsync(errors); } } -#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously } diff --git a/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj b/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj index f050be288..2d88ac50e 100644 --- a/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj +++ b/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj @@ -17,10 +17,10 @@ false false false - 1.2.9 + 1.3.0 Provides support to persist workflows running on Workflow Core to a MongoDB database. - 1.2.9.0 - 1.2.9.0 + 1.3.0.0 + 1.3.0.0 diff --git a/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj b/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj index 56099402a..c590229b0 100644 --- a/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj +++ b/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj @@ -18,9 +18,9 @@ false false Provides support to persist workflows running on Workflow Core to a PostgreSQL database. - 1.2.9 - 1.2.9.0 - 1.2.9.0 + 1.3.0 + 1.3.0.0 + 1.3.0.0 diff --git a/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj b/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj index c0216719b..85bb29bce 100644 --- a/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj +++ b/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj @@ -17,10 +17,10 @@ false false false - 1.2.9 + 1.3.0 Provides support to persist workflows running on Workflow Core to a SQL Server database. - 1.2.9.0 - 1.2.9.0 + 1.3.0.0 + 1.3.0.0 diff --git a/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj b/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj index 1d9faa42c..1cdc8487f 100644 --- a/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj +++ b/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj @@ -18,9 +18,9 @@ false false Provides support to persist workflows running on Workflow Core to a Sqlite database. - 1.2.9 - 1.2.9.0 - 1.2.9.0 + 1.3.0 + 1.3.0.0 + 1.3.0.0 diff --git a/src/providers/WorkflowCore.Providers.Azure/Services/AzureLockManager.cs b/src/providers/WorkflowCore.Providers.Azure/Services/AzureLockManager.cs index 341b036a0..2bc97ec67 100644 --- a/src/providers/WorkflowCore.Providers.Azure/Services/AzureLockManager.cs +++ b/src/providers/WorkflowCore.Providers.Azure/Services/AzureLockManager.cs @@ -30,7 +30,7 @@ public AzureLockManager(string connectionString, ILoggerFactory logFactory) _client = account.CreateCloudBlobClient(); } - public async Task AcquireLock(string Id) + public async Task AcquireLock(string Id, CancellationToken cancellationToken) { var blob = _container.GetBlockBlobReference(Id); diff --git a/src/providers/WorkflowCore.Providers.Azure/Services/AzureStorageQueueProvider.cs b/src/providers/WorkflowCore.Providers.Azure/Services/AzureStorageQueueProvider.cs index f335661f0..75be01fd1 100644 --- a/src/providers/WorkflowCore.Providers.Azure/Services/AzureStorageQueueProvider.cs +++ b/src/providers/WorkflowCore.Providers.Azure/Services/AzureStorageQueueProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.WindowsAzure.Storage; @@ -15,6 +16,8 @@ public class AzureStorageQueueProvider : IQueueProvider private readonly CloudQueue _workflowQueue; private readonly CloudQueue _eventQueue; + public bool IsDequeueBlocking => false; + public AzureStorageQueueProvider(string connectionString, ILoggerFactory logFactory) { _logger = logFactory.CreateLogger(); @@ -40,7 +43,7 @@ public async Task QueueWork(string id, QueueType queue) } } - public async Task DequeueWork(QueueType queue) + public async Task DequeueWork(QueueType queue, CancellationToken cancellationToken) { CloudQueue cloudQueue = null; switch (queue) @@ -55,7 +58,7 @@ public async Task DequeueWork(QueueType queue) if (cloudQueue == null) return null; - + var msg = await cloudQueue.GetMessageAsync(); if (msg == null) diff --git a/src/providers/WorkflowCore.Providers.Azure/WorkflowCore.Providers.Azure.csproj b/src/providers/WorkflowCore.Providers.Azure/WorkflowCore.Providers.Azure.csproj index e3f8f5ba7..a31afecd3 100644 --- a/src/providers/WorkflowCore.Providers.Azure/WorkflowCore.Providers.Azure.csproj +++ b/src/providers/WorkflowCore.Providers.Azure/WorkflowCore.Providers.Azure.csproj @@ -7,17 +7,19 @@ - Provides distributed lock management on Workflow Core - Provides Queueing support on Workflow Core workflow workflowcore dlm - 1.2.9 + 1.3.0 $(PackageTargetFallback);dnxcore50 https://github.com/danielgerlag/workflow-core https://github.com/danielgerlag/workflow-core/blob/master/LICENSE.md git https://github.com/danielgerlag/workflow-core.git Daniel Gerlag + 1.3.0.0 + 1.3.0.0 - + diff --git a/src/providers/WorkflowCore.QueueProviders.RabbitMQ/Services/RabbitMQProvider.cs b/src/providers/WorkflowCore.QueueProviders.RabbitMQ/Services/RabbitMQProvider.cs index 949058617..bbb0ffd87 100644 --- a/src/providers/WorkflowCore.QueueProviders.RabbitMQ/Services/RabbitMQProvider.cs +++ b/src/providers/WorkflowCore.QueueProviders.RabbitMQ/Services/RabbitMQProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using WorkflowCore.Interface; using WorkflowCore.Models; @@ -18,6 +19,8 @@ public class RabbitMQProvider : IQueueProvider private IConnection _connection = null; private static JsonSerializerSettings SerializerSettings = new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.All }; + public bool IsDequeueBlocking => false; + public RabbitMQProvider(IConnectionFactory connectionFactory) { _connectionFactory = connectionFactory; @@ -36,7 +39,7 @@ public async Task QueueWork(string id, QueueType queue) } } - public async Task DequeueWork(QueueType queue) + public async Task DequeueWork(QueueType queue, CancellationToken cancellationToken) { if (_connection == null) throw new Exception("RabbitMQ provider not running"); diff --git a/src/providers/WorkflowCore.QueueProviders.RabbitMQ/WorkflowCore.QueueProviders.RabbitMQ.csproj b/src/providers/WorkflowCore.QueueProviders.RabbitMQ/WorkflowCore.QueueProviders.RabbitMQ.csproj index 3acbfeb63..ca2cada13 100644 --- a/src/providers/WorkflowCore.QueueProviders.RabbitMQ/WorkflowCore.QueueProviders.RabbitMQ.csproj +++ b/src/providers/WorkflowCore.QueueProviders.RabbitMQ/WorkflowCore.QueueProviders.RabbitMQ.csproj @@ -17,8 +17,10 @@ false false false - 1.2.8 + 1.3.0 Queue provider for Workflow-core using RabbitMQ + 1.3.0.0 + 1.3.0.0 diff --git a/src/providers/WorkflowCore.QueueProviders.ZeroMQ/Services/ZeroMQProvider.cs b/src/providers/WorkflowCore.QueueProviders.ZeroMQ/Services/ZeroMQProvider.cs index e52998d1e..c6ac3d6ac 100644 --- a/src/providers/WorkflowCore.QueueProviders.ZeroMQ/Services/ZeroMQProvider.cs +++ b/src/providers/WorkflowCore.QueueProviders.ZeroMQ/Services/ZeroMQProvider.cs @@ -27,7 +27,8 @@ public class ZeroMQProvider : IQueueProvider private List _peerConnectionStrings; private string _localConnectionString; private bool _active = false; - + + public bool IsDequeueBlocking => false; public ZeroMQProvider(int port, IEnumerable peers, bool canTakeWork, ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); @@ -55,7 +56,7 @@ public async Task QueueWork(string id, QueueType queue) } } - public async Task DequeueWork(QueueType queue) + public async Task DequeueWork(QueueType queue, CancellationToken cancellationToken) { if (SelectQueue(queue).TryDequeue(out string id)) return id; diff --git a/src/providers/WorkflowCore.QueueProviders.ZeroMQ/WorkflowCore.QueueProviders.ZeroMQ.csproj b/src/providers/WorkflowCore.QueueProviders.ZeroMQ/WorkflowCore.QueueProviders.ZeroMQ.csproj index b92f69b5f..00917cf29 100644 --- a/src/providers/WorkflowCore.QueueProviders.ZeroMQ/WorkflowCore.QueueProviders.ZeroMQ.csproj +++ b/src/providers/WorkflowCore.QueueProviders.ZeroMQ/WorkflowCore.QueueProviders.ZeroMQ.csproj @@ -17,9 +17,9 @@ false false false - 1.2.8 - 1.2.8.0 - 1.2.8.0 + 1.3.0 + 1.3.0.0 + 1.3.0.0 diff --git a/src/samples/WorkflowCore.Sample03/Program.cs b/src/samples/WorkflowCore.Sample03/Program.cs index 776aa6df4..715c248f1 100644 --- a/src/samples/WorkflowCore.Sample03/Program.cs +++ b/src/samples/WorkflowCore.Sample03/Program.cs @@ -21,12 +21,14 @@ public static void Main(string[] args) host.RegisterWorkflow(); host.Start(); - var initialData = new MyDataClass(); - initialData.Value1 = 2; - initialData.Value2 = 3; + var initialData = new MyDataClass + { + Value1 = 2, + Value2 = 3 + }; host.StartWorkflow("PassingDataWorkflow", 1, initialData); - host.StartWorkflow("PassingDataWorkflow", 1, new MyDataClass() { Value1 = 3, Value2 = 3 }); + host.StartWorkflow("PassingDataWorkflow", 1, new MyDataClass() { Value1 = 3, Value2 = 4 }); Console.ReadLine(); host.Stop(); @@ -38,7 +40,7 @@ private static IServiceProvider ConfigureServices() IServiceCollection services = new ServiceCollection(); services.AddLogging(); services.AddWorkflow(); - //services.AddWorkflow(x => x.UseSqlServer(@"Server=.;Database=WorkflowCore;Trusted_Connection=True;")); + //services.AddWorkflow(x => x.UseSqlServer(@"Server=.\SQLEXPRESS;Database=WorkflowCore;Trusted_Connection=True;", true, true)); var serviceProvider = services.BuildServiceProvider(); //config logging diff --git a/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs b/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs index 209229812..c5933b3bc 100644 --- a/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs +++ b/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs @@ -18,6 +18,10 @@ public class AddNumbers : StepBodyAsync public override async Task RunAsync(IStepExecutionContext context) { + if (Input1 == 2) + //await Task.Delay(2000); + System.Threading.Thread.Sleep(2000); + Output = (Input1 + Input2); return ExecutionResult.Next(); } diff --git a/test/ScratchPad/Program.cs b/test/ScratchPad/Program.cs index 5a3158fe3..ab9fc7bff 100644 --- a/test/ScratchPad/Program.cs +++ b/test/ScratchPad/Program.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using WorkflowCore.Interface; using WorkflowCore.LockProviders.ZeroMQ.Services; @@ -30,9 +31,9 @@ public static void Main(string[] args) Peer1.QueueWork("Task 3", QueueType.Workflow).Wait(); System.Threading.Thread.Sleep(100); - var value1 = Peer1.DequeueWork(QueueType.Workflow).Result; - var value2 = Peer2.DequeueWork(QueueType.Workflow).Result; - var value3 = Peer3.DequeueWork(QueueType.Workflow).Result; + var value1 = Peer1.DequeueWork(QueueType.Workflow, new CancellationToken()).Result; + var value2 = Peer2.DequeueWork(QueueType.Workflow, new CancellationToken()).Result; + var value3 = Peer3.DequeueWork(QueueType.Workflow, new CancellationToken()).Result; Console.ReadLine(); } diff --git a/test/WorkflowCore.TestAssets/LockProvider/DistributedLockProviderTests.cs b/test/WorkflowCore.TestAssets/LockProvider/DistributedLockProviderTests.cs index f57f8cd5f..29f0a8f7e 100644 --- a/test/WorkflowCore.TestAssets/LockProvider/DistributedLockProviderTests.cs +++ b/test/WorkflowCore.TestAssets/LockProvider/DistributedLockProviderTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; using WorkflowCore.Interface; using NUnit; using FluentAssertions; @@ -26,9 +27,9 @@ public async void AcquiresLock() { const string lock1 = "lock1"; const string lock2 = "lock2"; - await Subject.AcquireLock(lock2); + await Subject.AcquireLock(lock2, new CancellationToken()); - var acquired = await Subject.AcquireLock(lock1); + var acquired = await Subject.AcquireLock(lock1, new CancellationToken()); acquired.Should().Be(true); } @@ -37,9 +38,9 @@ public async void AcquiresLock() public async void DoesNotAcquireWhenLocked() { const string lock1 = "lock1"; - await Subject.AcquireLock(lock1); + await Subject.AcquireLock(lock1, new CancellationToken()); - var acquired = await Subject.AcquireLock(lock1); + var acquired = await Subject.AcquireLock(lock1, new CancellationToken()); acquired.Should().Be(false); } @@ -48,11 +49,11 @@ public async void DoesNotAcquireWhenLocked() public async void ReleasesLock() { const string lock1 = "lock1"; - await Subject.AcquireLock(lock1); + await Subject.AcquireLock(lock1, new CancellationToken()); await Subject.ReleaseLock(lock1); - var available = await Subject.AcquireLock(lock1); + var available = await Subject.AcquireLock(lock1, new CancellationToken()); available.Should().Be(true); } diff --git a/test/WorkflowCore.Tests.ZeroMQ/LockProvider/AcquireLock.cs b/test/WorkflowCore.Tests.ZeroMQ/LockProvider/AcquireLock.cs index 08636ac87..ab9f1dee0 100644 --- a/test/WorkflowCore.Tests.ZeroMQ/LockProvider/AcquireLock.cs +++ b/test/WorkflowCore.Tests.ZeroMQ/LockProvider/AcquireLock.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using WorkflowCore.Interface; using WorkflowCore.LockProviders.ZeroMQ.Services; @@ -26,13 +27,13 @@ public class AcquireLock System.Threading.Thread.Sleep(1000); }; - Because of = () => lock_result = Peer1.AcquireLock("lock1").Result; + Because of = () => lock_result = Peer1.AcquireLock("lock1", new CancellationToken()).Result; It should_return_true = () => lock_result.ShouldBeTrue(); - It should_be_locked_on_peer1 = () => Peer1.AcquireLock("lock1").Result.ShouldBeFalse(); - It should_be_locked_on_peer2 = () => Peer2.AcquireLock("lock1").Result.ShouldBeFalse(); - It should_be_locked_on_peer3 = () => Peer3.AcquireLock("lock1").Result.ShouldBeFalse(); - It should_have_another_id_unlocked_on_peer3 = () => Peer3.AcquireLock("lock2").Result.ShouldBeTrue(); + It should_be_locked_on_peer1 = () => Peer1.AcquireLock("lock1", new CancellationToken()).Result.ShouldBeFalse(); + It should_be_locked_on_peer2 = () => Peer2.AcquireLock("lock1", new CancellationToken()).Result.ShouldBeFalse(); + It should_be_locked_on_peer3 = () => Peer3.AcquireLock("lock1", new CancellationToken()).Result.ShouldBeFalse(); + It should_have_another_id_unlocked_on_peer3 = () => Peer3.AcquireLock("lock2", new CancellationToken()).Result.ShouldBeTrue(); Cleanup after = () => { diff --git a/test/WorkflowCore.Tests.ZeroMQ/LockProvider/ReleaseLock.cs b/test/WorkflowCore.Tests.ZeroMQ/LockProvider/ReleaseLock.cs index 5cb0e4d41..4df269f85 100644 --- a/test/WorkflowCore.Tests.ZeroMQ/LockProvider/ReleaseLock.cs +++ b/test/WorkflowCore.Tests.ZeroMQ/LockProvider/ReleaseLock.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using WorkflowCore.Interface; using WorkflowCore.LockProviders.ZeroMQ.Services; @@ -24,12 +25,12 @@ public class ReleaseLock Peer2.Start(); Peer3.Start(); System.Threading.Thread.Sleep(1000); - Peer1.AcquireLock("lock1").Wait(); + Peer1.AcquireLock("lock1", new CancellationToken()).Wait(); }; Because of = () => Peer1.ReleaseLock("lock1").Wait(); - It should_be_lockable_on_peer1 = () => Peer1.AcquireLock("lock1").Result.ShouldBeTrue(); + It should_be_lockable_on_peer1 = () => Peer1.AcquireLock("lock1", new CancellationToken()).Result.ShouldBeTrue(); Cleanup after = () => { diff --git a/test/WorkflowCore.Tests.ZeroMQ/LockProvider/ReleaseLock_Exclusive.cs b/test/WorkflowCore.Tests.ZeroMQ/LockProvider/ReleaseLock_Exclusive.cs index c2127abf0..dedafa20d 100644 --- a/test/WorkflowCore.Tests.ZeroMQ/LockProvider/ReleaseLock_Exclusive.cs +++ b/test/WorkflowCore.Tests.ZeroMQ/LockProvider/ReleaseLock_Exclusive.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using WorkflowCore.Interface; using WorkflowCore.LockProviders.ZeroMQ.Services; @@ -24,12 +25,12 @@ public class ReleaseLock_Exclusive Peer2.Start(); Peer3.Start(); System.Threading.Thread.Sleep(1000); - Peer1.AcquireLock("lock1").Wait(); + Peer1.AcquireLock("lock1", new CancellationToken()).Wait(); }; Because of = () => Peer2.ReleaseLock("lock1").Wait(); - It should_not_be_lockable_on_peer2 = () => Peer2.AcquireLock("lock1").Result.ShouldBeFalse(); + It should_not_be_lockable_on_peer2 = () => Peer2.AcquireLock("lock1", new CancellationToken()).Result.ShouldBeFalse(); Cleanup after = () => { diff --git a/test/WorkflowCore.Tests.ZeroMQ/QueueProvider/MessageDistribution.cs b/test/WorkflowCore.Tests.ZeroMQ/QueueProvider/MessageDistribution.cs index b27267b04..efc3f6b81 100644 --- a/test/WorkflowCore.Tests.ZeroMQ/QueueProvider/MessageDistribution.cs +++ b/test/WorkflowCore.Tests.ZeroMQ/QueueProvider/MessageDistribution.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using WorkflowCore.Interface; using WorkflowCore.QueueProviders.ZeroMQ.Services; @@ -35,9 +36,9 @@ public class MessageDistribution It should_be_dequeued_once_on_any_peer = () => { - var result1 = Peer1.DequeueWork(QueueType.Workflow).Result; - var result2 = Peer2.DequeueWork(QueueType.Workflow).Result; - var result3 = Peer3.DequeueWork(QueueType.Workflow).Result; + var result1 = Peer1.DequeueWork(QueueType.Workflow, new CancellationToken()).Result; + var result2 = Peer2.DequeueWork(QueueType.Workflow, new CancellationToken()).Result; + var result3 = Peer3.DequeueWork(QueueType.Workflow, new CancellationToken()).Result; var oneResult = (result1 == "Task 1") ^ (result2 == "Task 1") ^ (result3 == "Task 1"); oneResult.ShouldBeTrue(); }; diff --git a/test/WorkflowCore.Tests.ZeroMQ/QueueProvider/MultiMessageDistribution.cs b/test/WorkflowCore.Tests.ZeroMQ/QueueProvider/MultiMessageDistribution.cs index d68423eed..ced147c03 100644 --- a/test/WorkflowCore.Tests.ZeroMQ/QueueProvider/MultiMessageDistribution.cs +++ b/test/WorkflowCore.Tests.ZeroMQ/QueueProvider/MultiMessageDistribution.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using WorkflowCore.Interface; using WorkflowCore.QueueProviders.ZeroMQ.Services; @@ -39,9 +40,9 @@ public class MultiMessageDistribution { string[] results = new string[] { - Peer1.DequeueWork(QueueType.Workflow).Result, - Peer2.DequeueWork(QueueType.Workflow).Result, - Peer3.DequeueWork(QueueType.Workflow).Result + Peer1.DequeueWork(QueueType.Workflow, new CancellationToken()).Result, + Peer2.DequeueWork(QueueType.Workflow, new CancellationToken()).Result, + Peer3.DequeueWork(QueueType.Workflow, new CancellationToken()).Result }; results.ShouldContain("Task 1"); From 5af8d2323ab9913bb8945877f2a79ee5e2dfd88f Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Mon, 17 Jul 2017 21:04:22 -0700 Subject: [PATCH 6/9] TPL dataflow action block --- .../Services/QueueTaskDispatcher.cs | 58 +++++++++---------- src/WorkflowCore/WorkflowCore.csproj | 1 + .../WorkflowCore.Sample03/Steps/AddNumbers.cs | 4 +- 3 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/WorkflowCore/Services/QueueTaskDispatcher.cs b/src/WorkflowCore/Services/QueueTaskDispatcher.cs index 6cfb6a027..90f6c72e6 100644 --- a/src/WorkflowCore/Services/QueueTaskDispatcher.cs +++ b/src/WorkflowCore/Services/QueueTaskDispatcher.cs @@ -1,9 +1,9 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; -using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; using WorkflowCore.Interface; using WorkflowCore.Models; @@ -17,8 +17,7 @@ public abstract class QueueTaskDispatcher : IBackgroundTask protected readonly IQueueProvider QueueProvider; protected readonly ILogger Logger; protected readonly WorkflowOptions Options; - protected Task DispatchTask; - private SemaphoreSlim _semaphore; + protected Task DispatchTask; private CancellationTokenSource _cancellationTokenSource; protected QueueTaskDispatcher(IQueueProvider queueProvider, ILoggerFactory loggerFactory, WorkflowOptions options) @@ -36,7 +35,7 @@ public virtual void Start() throw new InvalidOperationException(); _cancellationTokenSource = new CancellationTokenSource(); - _semaphore = new SemaphoreSlim(MaxConcurrentItems); + DispatchTask = new Task(Execute); DispatchTask.Start(); } @@ -45,41 +44,38 @@ public virtual void Stop() { _cancellationTokenSource.Cancel(); DispatchTask.Wait(); - - for (var i = 0; i < MaxConcurrentItems; i++) - _semaphore.Wait(); - DispatchTask = null; } private async void Execute() { var cancelToken = _cancellationTokenSource.Token; + var opts = new ExecutionDataflowBlockOptions() + { + MaxDegreeOfParallelism = MaxConcurrentItems, + BoundedCapacity = MaxConcurrentItems + 1 + }; + + var actionBlock = new ActionBlock(ExecuteItem, opts); + while (!cancelToken.IsCancellationRequested) { try { - await _semaphore.WaitAsync(cancelToken); - string item; - try - { - item = await QueueProvider.DequeueWork(Queue, cancelToken); - } - catch + if (SpinWait.SpinUntil(() => actionBlock.InputCount == 0, Options.IdleTime)) { - _semaphore.Release(); - throw; - } + var item = await QueueProvider.DequeueWork(Queue, cancelToken); - if (item == null) - { - _semaphore.Release(); - if (!QueueProvider.IsDequeueBlocking) - await Task.Delay(Options.IdleTime, cancelToken); - continue; - } + if (item == null) + { + if (!QueueProvider.IsDequeueBlocking) + await Task.Delay(Options.IdleTime, cancelToken); + continue; + } - new Task(() => ExecuteItem(item, cancelToken)).Start(); + if (!actionBlock.Post(item)) + await QueueProvider.QueueWork(item, Queue); + } } catch (OperationCanceledException) { @@ -89,13 +85,15 @@ private async void Execute() Logger.LogError(ex.Message); } } + actionBlock.Complete(); + await actionBlock.Completion; } - private async void ExecuteItem(string itemId, CancellationToken cancellationToken) + private async Task ExecuteItem(string itemId) { try { - await ProcessItem(itemId, cancellationToken); + await ProcessItem(itemId, _cancellationTokenSource.Token); } catch (OperationCanceledException) { @@ -105,10 +103,6 @@ private async void ExecuteItem(string itemId, CancellationToken cancellationToke { Logger.LogError($"Error executing item {itemId} - {ex.Message}"); } - finally - { - _semaphore.Release(); - } } } } diff --git a/src/WorkflowCore/WorkflowCore.csproj b/src/WorkflowCore/WorkflowCore.csproj index 6bb04f1c2..24a8afd22 100644 --- a/src/WorkflowCore/WorkflowCore.csproj +++ b/src/WorkflowCore/WorkflowCore.csproj @@ -27,6 +27,7 @@ + diff --git a/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs b/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs index c5933b3bc..3d7e03d5b 100644 --- a/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs +++ b/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs @@ -19,8 +19,8 @@ public class AddNumbers : StepBodyAsync public override async Task RunAsync(IStepExecutionContext context) { if (Input1 == 2) - //await Task.Delay(2000); - System.Threading.Thread.Sleep(2000); + await Task.Delay(2000); + //System.Threading.Thread.Sleep(2000); Output = (Input1 + Input2); return ExecutionResult.Next(); From 1a7d9a931fd16e943f3648eac714b0a0c985bc49 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Tue, 18 Jul 2017 20:31:38 -0700 Subject: [PATCH 7/9] reorg resource pools --- .../Interface/IPersistenceProvider.cs | 4 +- .../ServiceCollectionExtensions.cs | 9 +- .../EventConsumer.cs} | 12 +- .../QueueConsumer.cs} | 33 +++-- .../{ => BackgroundTasks}/RunnablePoller.cs | 14 +-- .../BackgroundTasks/WorkflowConsumer.cs | 119 ++++++++++++++++++ .../MemoryPersistenceProvider.cs | 8 +- .../SingleNodeLockProvider.cs | 0 .../SingleNodeQueueProvider.cs | 0 .../ParallelStepBuilder.cs | 0 .../{ => FluentBuilders}/ReturnStepBuilder.cs | 0 .../{ => FluentBuilders}/StepBuilder.cs | 0 .../StepOutcomeBuilder.cs | 0 .../{ => FluentBuilders}/WorkflowBuilder.cs | 0 .../Services/InjectedObjectPoolPolicy.cs | 28 +++++ src/WorkflowCore/Services/WorkflowHost.cs | 1 + .../Services/WorkflowTaskDispatcher.cs | 111 ---------------- src/WorkflowCore/WorkflowCore.csproj | 1 + .../EntityFrameworkPersistenceProvider.cs | 8 +- .../Services/MongoPersistenceProvider.cs | 8 +- 20 files changed, 197 insertions(+), 159 deletions(-) rename src/WorkflowCore/Services/{EventTaskDispatcher.cs => BackgroundTasks/EventConsumer.cs} (87%) rename src/WorkflowCore/Services/{QueueTaskDispatcher.cs => BackgroundTasks/QueueConsumer.cs} (75%) rename src/WorkflowCore/Services/{ => BackgroundTasks}/RunnablePoller.cs (94%) create mode 100644 src/WorkflowCore/Services/BackgroundTasks/WorkflowConsumer.cs rename src/WorkflowCore/Services/{ => DefaultProviders}/MemoryPersistenceProvider.cs (97%) rename src/WorkflowCore/Services/{ => DefaultProviders}/SingleNodeLockProvider.cs (100%) rename src/WorkflowCore/Services/{ => DefaultProviders}/SingleNodeQueueProvider.cs (100%) rename src/WorkflowCore/Services/{ => FluentBuilders}/ParallelStepBuilder.cs (100%) rename src/WorkflowCore/Services/{ => FluentBuilders}/ReturnStepBuilder.cs (100%) rename src/WorkflowCore/Services/{ => FluentBuilders}/StepBuilder.cs (100%) rename src/WorkflowCore/Services/{ => FluentBuilders}/StepOutcomeBuilder.cs (100%) rename src/WorkflowCore/Services/{ => FluentBuilders}/WorkflowBuilder.cs (100%) create mode 100644 src/WorkflowCore/Services/InjectedObjectPoolPolicy.cs delete mode 100644 src/WorkflowCore/Services/WorkflowTaskDispatcher.cs diff --git a/src/WorkflowCore/Interface/IPersistenceProvider.cs b/src/WorkflowCore/Interface/IPersistenceProvider.cs index 10601f39f..38447abc4 100644 --- a/src/WorkflowCore/Interface/IPersistenceProvider.cs +++ b/src/WorkflowCore/Interface/IPersistenceProvider.cs @@ -16,7 +16,7 @@ public interface IPersistenceProvider Task PersistWorkflow(WorkflowInstance workflow); - Task> GetRunnableInstances(); + Task> GetRunnableInstances(DateTime asAt); Task> GetWorkflowInstances(WorkflowStatus? status, string type, DateTime? createdFrom, DateTime? createdTo, int skip, int take); @@ -32,7 +32,7 @@ public interface IPersistenceProvider Task GetEvent(string id); - Task> GetRunnableEvents(); + Task> GetRunnableEvents(DateTime asAt); Task> GetEvents(string eventName, string eventKey, DateTime asOf); diff --git a/src/WorkflowCore/ServiceCollectionExtensions.cs b/src/WorkflowCore/ServiceCollectionExtensions.cs index 5bdc3c6c5..b973fcca4 100644 --- a/src/WorkflowCore/ServiceCollectionExtensions.cs +++ b/src/WorkflowCore/ServiceCollectionExtensions.cs @@ -7,7 +7,9 @@ using WorkflowCore.Services; using WorkflowCore.Models; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; using WorkflowCore.Primitives; +using WorkflowCore.Services.BackgroundTasks; namespace Microsoft.Extensions.DependencyInjection { @@ -24,8 +26,8 @@ public static void AddWorkflow(this IServiceCollection services, Action(); services.AddSingleton(options); - services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddSingleton(); @@ -33,6 +35,9 @@ public static void AddWorkflow(this IServiceCollection services, Action(); services.AddTransient(); + services.AddTransient, InjectedObjectPoolPolicy>(); + services.AddTransient, InjectedObjectPoolPolicy>(); + services.AddTransient(); } } diff --git a/src/WorkflowCore/Services/EventTaskDispatcher.cs b/src/WorkflowCore/Services/BackgroundTasks/EventConsumer.cs similarity index 87% rename from src/WorkflowCore/Services/EventTaskDispatcher.cs rename to src/WorkflowCore/Services/BackgroundTasks/EventConsumer.cs index 01ad01856..e946bed25 100644 --- a/src/WorkflowCore/Services/EventTaskDispatcher.cs +++ b/src/WorkflowCore/Services/BackgroundTasks/EventConsumer.cs @@ -1,16 +1,14 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; +using System; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using WorkflowCore.Interface; using WorkflowCore.Models; -namespace WorkflowCore.Services +namespace WorkflowCore.Services.BackgroundTasks { - class EventTaskDispatcher : QueueTaskDispatcher, IBackgroundTask + internal class EventConsumer : QueueConsumer, IBackgroundTask { private readonly IPersistenceProvider _persistenceStore; private readonly IDistributedLockProvider _lockProvider; @@ -18,7 +16,7 @@ class EventTaskDispatcher : QueueTaskDispatcher, IBackgroundTask protected override QueueType Queue => QueueType.Event; - public EventTaskDispatcher(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, WorkflowOptions options, IDateTimeProvider datetimeProvider) + public EventConsumer(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, WorkflowOptions options, IDateTimeProvider datetimeProvider) : base(queueProvider, loggerFactory, options) { _persistenceStore = persistenceStore; diff --git a/src/WorkflowCore/Services/QueueTaskDispatcher.cs b/src/WorkflowCore/Services/BackgroundTasks/QueueConsumer.cs similarity index 75% rename from src/WorkflowCore/Services/QueueTaskDispatcher.cs rename to src/WorkflowCore/Services/BackgroundTasks/QueueConsumer.cs index 90f6c72e6..b1e6b6721 100644 --- a/src/WorkflowCore/Services/QueueTaskDispatcher.cs +++ b/src/WorkflowCore/Services/BackgroundTasks/QueueConsumer.cs @@ -1,15 +1,14 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; +using System; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; +using Microsoft.Extensions.Logging; using WorkflowCore.Interface; using WorkflowCore.Models; -namespace WorkflowCore.Services +namespace WorkflowCore.Services.BackgroundTasks { - public abstract class QueueTaskDispatcher : IBackgroundTask + internal abstract class QueueConsumer : IBackgroundTask { protected abstract QueueType Queue { get; } protected virtual int MaxConcurrentItems => Math.Max(Environment.ProcessorCount, 2); @@ -20,7 +19,7 @@ public abstract class QueueTaskDispatcher : IBackgroundTask protected Task DispatchTask; private CancellationTokenSource _cancellationTokenSource; - protected QueueTaskDispatcher(IQueueProvider queueProvider, ILoggerFactory loggerFactory, WorkflowOptions options) + protected QueueConsumer(IQueueProvider queueProvider, ILoggerFactory loggerFactory, WorkflowOptions options) { QueueProvider = queueProvider; Options = options; @@ -62,20 +61,20 @@ private async void Execute() { try { - if (SpinWait.SpinUntil(() => actionBlock.InputCount == 0, Options.IdleTime)) - { - var item = await QueueProvider.DequeueWork(Queue, cancelToken); + if (!SpinWait.SpinUntil(() => actionBlock.InputCount == 0, Options.IdleTime)) + continue; - if (item == null) - { - if (!QueueProvider.IsDequeueBlocking) - await Task.Delay(Options.IdleTime, cancelToken); - continue; - } + var item = await QueueProvider.DequeueWork(Queue, cancelToken); - if (!actionBlock.Post(item)) - await QueueProvider.QueueWork(item, Queue); + if (item == null) + { + if (!QueueProvider.IsDequeueBlocking) + await Task.Delay(Options.IdleTime, cancelToken); + continue; } + + if (!actionBlock.Post(item)) + await QueueProvider.QueueWork(item, Queue); } catch (OperationCanceledException) { diff --git a/src/WorkflowCore/Services/RunnablePoller.cs b/src/WorkflowCore/Services/BackgroundTasks/RunnablePoller.cs similarity index 94% rename from src/WorkflowCore/Services/RunnablePoller.cs rename to src/WorkflowCore/Services/BackgroundTasks/RunnablePoller.cs index ecb77e3ad..22c136ad1 100644 --- a/src/WorkflowCore/Services/RunnablePoller.cs +++ b/src/WorkflowCore/Services/BackgroundTasks/RunnablePoller.cs @@ -1,15 +1,13 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; +using System; using System.Linq; -using System.Text; using System.Threading; +using Microsoft.Extensions.Logging; using WorkflowCore.Interface; using WorkflowCore.Models; -namespace WorkflowCore.Services +namespace WorkflowCore.Services.BackgroundTasks { - class RunnablePoller : IBackgroundTask + internal class RunnablePoller : IBackgroundTask { private readonly IPersistenceProvider _persistenceStore; private readonly IDistributedLockProvider _lockProvider; @@ -54,7 +52,7 @@ private async void PollRunnables(object target) try { _logger.LogInformation("Polling for runnable workflows"); - var runnables = await _persistenceStore.GetRunnableInstances(); + var runnables = await _persistenceStore.GetRunnableInstances(DateTime.Now); foreach (var item in runnables) { _logger.LogDebug("Got runnable instance {0}", item); @@ -79,7 +77,7 @@ private async void PollRunnables(object target) try { _logger.LogInformation("Polling for unprocessed events"); - var events = await _persistenceStore.GetRunnableEvents(); + var events = await _persistenceStore.GetRunnableEvents(DateTime.Now); foreach (var item in events.ToList()) { _logger.LogDebug($"Got unprocessed event {item}"); diff --git a/src/WorkflowCore/Services/BackgroundTasks/WorkflowConsumer.cs b/src/WorkflowCore/Services/BackgroundTasks/WorkflowConsumer.cs new file mode 100644 index 000000000..62b5100ca --- /dev/null +++ b/src/WorkflowCore/Services/BackgroundTasks/WorkflowConsumer.cs @@ -0,0 +1,119 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; +using WorkflowCore.Interface; +using WorkflowCore.Models; + +namespace WorkflowCore.Services.BackgroundTasks +{ + internal class WorkflowConsumer : QueueConsumer, IBackgroundTask + { + private readonly IDistributedLockProvider _lockProvider; + private readonly IDateTimeProvider _datetimeProvider; + private readonly ObjectPool _persistenceStorePool; + private readonly ObjectPool _executorPool; + + protected override QueueType Queue => QueueType.Workflow; + + public WorkflowConsumer(IPooledObjectPolicy persistencePoolPolicy, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, IPooledObjectPolicy executorPoolPolicy, IDateTimeProvider datetimeProvider, WorkflowOptions options) + : base(queueProvider, loggerFactory, options) + { + _persistenceStorePool = new DefaultObjectPool(persistencePoolPolicy); + _executorPool = new DefaultObjectPool(executorPoolPolicy); + _lockProvider = lockProvider; + _datetimeProvider = datetimeProvider; + } + + protected override async Task ProcessItem(string itemId, CancellationToken cancellationToken) + { + if (await _lockProvider.AcquireLock(itemId, cancellationToken)) + { + WorkflowInstance workflow = null; + WorkflowExecutorResult result = null; + var persistenceStore = _persistenceStorePool.Get(); + try + { + try + { + cancellationToken.ThrowIfCancellationRequested(); + workflow = await persistenceStore.GetWorkflowInstance(itemId); + if (workflow.Status == WorkflowStatus.Runnable) + { + var executor = _executorPool.Get(); + try + { + result = await executor.Execute(workflow, Options); + } + finally + { + _executorPool.Return(executor); + await persistenceStore.PersistWorkflow(workflow); + } + } + } + finally + { + await _lockProvider.ReleaseLock(itemId); + if ((workflow != null) && (result != null)) + { + foreach (var sub in result.Subscriptions) + await SubscribeEvent(sub, persistenceStore); + + await persistenceStore.PersistErrors(result.Errors); + + var readAheadTicks = _datetimeProvider.Now.Add(Options.PollInterval).ToUniversalTime().Ticks; + + if ((workflow.Status == WorkflowStatus.Runnable) && workflow.NextExecution.HasValue && workflow.NextExecution.Value < readAheadTicks) + { + new Task(() => FutureQueue(workflow, cancellationToken)).Start(); + } + } + } + } + finally + { + _persistenceStorePool.Return(persistenceStore); + } + } + else + { + Logger.LogInformation("Workflow locked {0}", itemId); + } + } + + private async Task SubscribeEvent(EventSubscription subscription, IPersistenceProvider persistenceStore) + { + //TODO: move to own class + Logger.LogDebug("Subscribing to event {0} {1} for workflow {2} step {3}", subscription.EventName, subscription.EventKey, subscription.WorkflowId, subscription.StepId); + + await persistenceStore.CreateEventSubscription(subscription); + var events = await persistenceStore.GetEvents(subscription.EventName, subscription.EventKey, subscription.SubscribeAsOf); + foreach (var evt in events) + { + await persistenceStore.MarkEventUnprocessed(evt); + await QueueProvider.QueueWork(evt, QueueType.Event); + } + } + + private async void FutureQueue(WorkflowInstance workflow, CancellationToken cancellationToken) + { + try + { + if (!workflow.NextExecution.HasValue) + return; + + var target = (workflow.NextExecution.Value - _datetimeProvider.Now.ToUniversalTime().Ticks); + if (target > 0) + await Task.Delay(TimeSpan.FromTicks(target), cancellationToken); + + await QueueProvider.QueueWork(workflow.Id, QueueType.Workflow); + } + catch (Exception ex) + { + Logger.LogError(ex.Message); + } + } + } +} diff --git a/src/WorkflowCore/Services/MemoryPersistenceProvider.cs b/src/WorkflowCore/Services/DefaultProviders/MemoryPersistenceProvider.cs similarity index 97% rename from src/WorkflowCore/Services/MemoryPersistenceProvider.cs rename to src/WorkflowCore/Services/DefaultProviders/MemoryPersistenceProvider.cs index c7f7f7508..639d6702a 100644 --- a/src/WorkflowCore/Services/MemoryPersistenceProvider.cs +++ b/src/WorkflowCore/Services/DefaultProviders/MemoryPersistenceProvider.cs @@ -40,11 +40,11 @@ public async Task PersistWorkflow(WorkflowInstance workflow) } } - public async Task> GetRunnableInstances() + public async Task> GetRunnableInstances(DateTime asAt) { lock (_instances) { - var now = DateTime.Now.ToUniversalTime().Ticks; + var now = asAt.ToUniversalTime().Ticks; return _instances.Where(x => x.NextExecution.HasValue && x.NextExecution <= now).Select(x => x.Id).ToList(); } } @@ -132,13 +132,13 @@ public async Task MarkEventProcessed(string id) } } - public async Task> GetRunnableEvents() + public async Task> GetRunnableEvents(DateTime asAt) { lock (_events) { return _events .Where(x => !x.IsProcessed) - .Where(x => x.EventTime <= DateTime.Now.ToUniversalTime()) + .Where(x => x.EventTime <= asAt.ToUniversalTime()) .Select(x => x.Id) .ToList(); } diff --git a/src/WorkflowCore/Services/SingleNodeLockProvider.cs b/src/WorkflowCore/Services/DefaultProviders/SingleNodeLockProvider.cs similarity index 100% rename from src/WorkflowCore/Services/SingleNodeLockProvider.cs rename to src/WorkflowCore/Services/DefaultProviders/SingleNodeLockProvider.cs diff --git a/src/WorkflowCore/Services/SingleNodeQueueProvider.cs b/src/WorkflowCore/Services/DefaultProviders/SingleNodeQueueProvider.cs similarity index 100% rename from src/WorkflowCore/Services/SingleNodeQueueProvider.cs rename to src/WorkflowCore/Services/DefaultProviders/SingleNodeQueueProvider.cs diff --git a/src/WorkflowCore/Services/ParallelStepBuilder.cs b/src/WorkflowCore/Services/FluentBuilders/ParallelStepBuilder.cs similarity index 100% rename from src/WorkflowCore/Services/ParallelStepBuilder.cs rename to src/WorkflowCore/Services/FluentBuilders/ParallelStepBuilder.cs diff --git a/src/WorkflowCore/Services/ReturnStepBuilder.cs b/src/WorkflowCore/Services/FluentBuilders/ReturnStepBuilder.cs similarity index 100% rename from src/WorkflowCore/Services/ReturnStepBuilder.cs rename to src/WorkflowCore/Services/FluentBuilders/ReturnStepBuilder.cs diff --git a/src/WorkflowCore/Services/StepBuilder.cs b/src/WorkflowCore/Services/FluentBuilders/StepBuilder.cs similarity index 100% rename from src/WorkflowCore/Services/StepBuilder.cs rename to src/WorkflowCore/Services/FluentBuilders/StepBuilder.cs diff --git a/src/WorkflowCore/Services/StepOutcomeBuilder.cs b/src/WorkflowCore/Services/FluentBuilders/StepOutcomeBuilder.cs similarity index 100% rename from src/WorkflowCore/Services/StepOutcomeBuilder.cs rename to src/WorkflowCore/Services/FluentBuilders/StepOutcomeBuilder.cs diff --git a/src/WorkflowCore/Services/WorkflowBuilder.cs b/src/WorkflowCore/Services/FluentBuilders/WorkflowBuilder.cs similarity index 100% rename from src/WorkflowCore/Services/WorkflowBuilder.cs rename to src/WorkflowCore/Services/FluentBuilders/WorkflowBuilder.cs diff --git a/src/WorkflowCore/Services/InjectedObjectPoolPolicy.cs b/src/WorkflowCore/Services/InjectedObjectPoolPolicy.cs new file mode 100644 index 000000000..2bd8a7b77 --- /dev/null +++ b/src/WorkflowCore/Services/InjectedObjectPoolPolicy.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.DependencyInjection; + +namespace WorkflowCore.Services +{ + internal class InjectedObjectPoolPolicy : IPooledObjectPolicy + { + private readonly IServiceProvider _provider; + + public InjectedObjectPoolPolicy(IServiceProvider provider) + { + _provider = provider; + } + + public T Create() + { + return _provider.GetService(); + } + + public bool Return(T obj) + { + return true; + } + } +} diff --git a/src/WorkflowCore/Services/WorkflowHost.cs b/src/WorkflowCore/Services/WorkflowHost.cs index 9c9a51b2e..cd76dfd6e 100644 --- a/src/WorkflowCore/Services/WorkflowHost.cs +++ b/src/WorkflowCore/Services/WorkflowHost.cs @@ -94,6 +94,7 @@ public async Task StartWorkflow(string workflowId, int? version, public void Start() { _shutdown = false; + PersistenceStore.EnsureStoreExists(); QueueProvider.Start().Wait(); LockProvider.Start().Wait(); diff --git a/src/WorkflowCore/Services/WorkflowTaskDispatcher.cs b/src/WorkflowCore/Services/WorkflowTaskDispatcher.cs deleted file mode 100644 index 7f5ad82b0..000000000 --- a/src/WorkflowCore/Services/WorkflowTaskDispatcher.cs +++ /dev/null @@ -1,111 +0,0 @@ -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using WorkflowCore.Interface; -using WorkflowCore.Models; - -namespace WorkflowCore.Services -{ - class WorkflowTaskDispatcher : QueueTaskDispatcher, IBackgroundTask - { - private readonly IPersistenceProvider _persistenceStore; - private readonly IDistributedLockProvider _lockProvider; - private readonly IWorkflowExecutor _executor; - private readonly IDateTimeProvider _datetimeProvider; - - protected override QueueType Queue => QueueType.Workflow; - - public WorkflowTaskDispatcher(IPersistenceProvider persistenceStore, IQueueProvider queueProvider, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IWorkflowRegistry registry, IDistributedLockProvider lockProvider, IWorkflowExecutor executor, IDateTimeProvider datetimeProvider, WorkflowOptions options) - : base(queueProvider, loggerFactory, options) - { - _persistenceStore = persistenceStore; - _executor = executor; - _lockProvider = lockProvider; - _datetimeProvider = datetimeProvider; - persistenceStore.EnsureStoreExists(); - } - - protected override async Task ProcessItem(string itemId, CancellationToken cancellationToken) - { - if (await _lockProvider.AcquireLock(itemId, cancellationToken)) - { - WorkflowInstance workflow = null; - WorkflowExecutorResult result = null; - try - { - cancellationToken.ThrowIfCancellationRequested(); - workflow = await _persistenceStore.GetWorkflowInstance(itemId); - if (workflow.Status == WorkflowStatus.Runnable) - { - try - { - result = await _executor.Execute(workflow, Options); - } - finally - { - await _persistenceStore.PersistWorkflow(workflow); - } - } - } - finally - { - await _lockProvider.ReleaseLock(itemId); - if ((workflow != null) && (result != null)) - { - foreach (var sub in result.Subscriptions) - await SubscribeEvent(sub); - - await _persistenceStore.PersistErrors(result.Errors); - - var readAheadTicks = _datetimeProvider.Now.Add(Options.PollInterval).ToUniversalTime().Ticks; - - if ((workflow.Status == WorkflowStatus.Runnable) && workflow.NextExecution.HasValue && workflow.NextExecution.Value < readAheadTicks) - { - new Task(() => FutureQueue(workflow, cancellationToken)).Start(); - } - } - } - } - else - { - Logger.LogInformation("Workflow locked {0}", itemId); - } - } - - private async Task SubscribeEvent(EventSubscription subscription) - { - //TODO: move to own class - Logger.LogDebug("Subscribing to event {0} {1} for workflow {2} step {3}", subscription.EventName, subscription.EventKey, subscription.WorkflowId, subscription.StepId); - - await _persistenceStore.CreateEventSubscription(subscription); - var events = await _persistenceStore.GetEvents(subscription.EventName, subscription.EventKey, subscription.SubscribeAsOf); - foreach (var evt in events) - { - await _persistenceStore.MarkEventUnprocessed(evt); - await QueueProvider.QueueWork(evt, QueueType.Event); - } - } - - private async void FutureQueue(WorkflowInstance workflow, CancellationToken cancellationToken) - { - try - { - if (!workflow.NextExecution.HasValue) - return; - - var target = (workflow.NextExecution.Value - _datetimeProvider.Now.ToUniversalTime().Ticks); - if (target > 0) - await Task.Delay(TimeSpan.FromTicks(target), cancellationToken); - - await QueueProvider.QueueWork(workflow.Id, QueueType.Workflow); - } - catch (Exception ex) - { - Logger.LogError(ex.Message); - } - } - } -} diff --git a/src/WorkflowCore/WorkflowCore.csproj b/src/WorkflowCore/WorkflowCore.csproj index 24a8afd22..985d45d31 100644 --- a/src/WorkflowCore/WorkflowCore.csproj +++ b/src/WorkflowCore/WorkflowCore.csproj @@ -27,6 +27,7 @@ + diff --git a/src/providers/WorkflowCore.Persistence.EntityFramework/Services/EntityFrameworkPersistenceProvider.cs b/src/providers/WorkflowCore.Persistence.EntityFramework/Services/EntityFrameworkPersistenceProvider.cs index 14961d7b7..5477d9c12 100644 --- a/src/providers/WorkflowCore.Persistence.EntityFramework/Services/EntityFrameworkPersistenceProvider.cs +++ b/src/providers/WorkflowCore.Persistence.EntityFramework/Services/EntityFrameworkPersistenceProvider.cs @@ -103,12 +103,12 @@ public async Task CreateNewWorkflow(WorkflowInstance workflow) } } - public async Task> GetRunnableInstances() + public async Task> GetRunnableInstances(DateTime asAt) { _mutex.WaitOne(); try { - var now = DateTime.Now.ToUniversalTime().Ticks; + var now = asAt.ToUniversalTime().Ticks; var raw = await Set() .Where(x => x.NextExecution.HasValue && (x.NextExecution <= now) && (x.Status == WorkflowStatus.Runnable)) .Select(x => x.InstanceId) @@ -300,9 +300,9 @@ public async Task GetEvent(string id) } } - public async Task> GetRunnableEvents() + public async Task> GetRunnableEvents(DateTime asAt) { - var now = DateTime.Now.ToUniversalTime(); + var now = asAt.ToUniversalTime(); _mutex.WaitOne(); try { diff --git a/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs b/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs index 4456249e1..c4934bf55 100644 --- a/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs +++ b/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs @@ -109,9 +109,9 @@ public async Task PersistWorkflow(WorkflowInstance workflow) await WorkflowInstances.ReplaceOneAsync(x => x.Id == workflow.Id, workflow); } - public async Task> GetRunnableInstances() + public async Task> GetRunnableInstances(DateTime asAt) { - var now = DateTime.Now.ToUniversalTime().Ticks; + var now = asAt.ToUniversalTime().Ticks; var query = WorkflowInstances .Find(x => x.NextExecution.HasValue && (x.NextExecution <= now) && (x.Status == WorkflowStatus.Runnable)) .Project(x => x.Id); @@ -180,9 +180,9 @@ public async Task GetEvent(string id) return await result.FirstAsync(); } - public async Task> GetRunnableEvents() + public async Task> GetRunnableEvents(DateTime asAt) { - var now = DateTime.Now.ToUniversalTime(); + var now = asAt.ToUniversalTime(); var query = Events .Find(x => !x.IsProcessed && x.EventTime <= now) .Project(x => x.Id); From edf39c95e67ef2bfcf6da5f7864e545b531c7f35 Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Sat, 22 Jul 2017 08:30:12 -0700 Subject: [PATCH 8/9] ref field --- ReleaseNotes/1.3.0.md | 20 ++ WorkflowCore.sln | 81 ++--- src/WorkflowCore/Models/WorkflowInstance.cs | 2 + .../WorkflowCore.WebAPI.csproj | 4 +- .../ExtensionMethods.cs | 2 + .../Models/PersistedWorkflow.cs | 3 + ...lowCore.Persistence.EntityFramework.csproj | 6 +- .../Services/MongoPersistenceProvider.cs | 1 + .../WorkflowCore.Persistence.MongoDB.csproj | 4 +- .../20170722200412_WfReference.Designer.cs | 259 ++++++++++++++++ .../Migrations/20170722200412_WfReference.cs | 27 ++ ...ostgresPersistenceProviderModelSnapshot.cs | 287 +++++++++--------- ...WorkflowCore.Persistence.PostgreSQL.csproj | 4 +- .../20170722195832_WfReference.Designer.cs | 265 ++++++++++++++++ .../Migrations/20170722195832_WfReference.cs | 27 ++ ...lServerPersistenceProviderModelSnapshot.cs | 5 +- .../WorkflowCore.Persistence.SqlServer.csproj | 6 +- .../WorkflowCore.Persistence.Sqlite.csproj | 2 +- ...orkflowCore.QueueProviders.RabbitMQ.csproj | 4 +- src/samples/WorkflowCore.Sample03/Program.cs | 1 - .../WorkflowCore.Sample03/Steps/AddNumbers.cs | 4 - 21 files changed, 808 insertions(+), 206 deletions(-) create mode 100644 ReleaseNotes/1.3.0.md create mode 100644 src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/20170722200412_WfReference.Designer.cs create mode 100644 src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/20170722200412_WfReference.cs create mode 100644 src/providers/WorkflowCore.Persistence.SqlServer/Migrations/20170722195832_WfReference.Designer.cs create mode 100644 src/providers/WorkflowCore.Persistence.SqlServer/Migrations/20170722195832_WfReference.cs diff --git a/ReleaseNotes/1.3.0.md b/ReleaseNotes/1.3.0.md new file mode 100644 index 000000000..3208d65d1 --- /dev/null +++ b/ReleaseNotes/1.3.0.md @@ -0,0 +1,20 @@ +# Workflow Core 1.3.0 + +* Added support for async steps + +Simply inherit from `StepBodyAsync` instead of `StepBody` + +```c# +public class DoSomething : StepBodyAsync +{ + public override async Task RunAsync(IStepExecutionContext context) + { + await Task.Delay(2000); + return ExecutionResult.Next(); + } +} +``` + +* Migrated from managing own thread pool to TPL datablocks for queue consumers + +* After executing a workflow, will determine if it is scheduled to run before the next poll, if so, will delay queue it \ No newline at end of file diff --git a/WorkflowCore.sln b/WorkflowCore.sln index 04f3f7711..782386412 100644 --- a/WorkflowCore.sln +++ b/WorkflowCore.sln @@ -18,91 +18,92 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E6CEAD8D-F EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "extensions", "extensions", "{6803696C-B19A-4B27-9193-082A02B6F205}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore", "src\WorkflowCore\WorkflowCore.csproj", "{B7B2EA4D-E7F0-43E2-942A-3A5AA8F57272}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore", "src\WorkflowCore\WorkflowCore.csproj", "{B7B2EA4D-E7F0-43E2-942A-3A5AA8F57272}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Persistence.MongoDB", "src\providers\WorkflowCore.Persistence.MongoDB\WorkflowCore.Persistence.MongoDB.csproj", "{DD26E7B4-9D3A-4E1E-8585-862DB6DE21EB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Persistence.MongoDB", "src\providers\WorkflowCore.Persistence.MongoDB\WorkflowCore.Persistence.MongoDB.csproj", "{DD26E7B4-9D3A-4E1E-8585-862DB6DE21EB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample01", "src\samples\WorkflowCore.Sample01\WorkflowCore.Sample01.csproj", "{660FEDAB-D085-476B-9E16-73E42F66DB4F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample01", "src\samples\WorkflowCore.Sample01\WorkflowCore.Sample01.csproj", "{660FEDAB-D085-476B-9E16-73E42F66DB4F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample02", "src\samples\WorkflowCore.Sample02\WorkflowCore.Sample02.csproj", "{BC6F28F1-9F47-4FFE-AD06-2B74CB89E76B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample02", "src\samples\WorkflowCore.Sample02\WorkflowCore.Sample02.csproj", "{BC6F28F1-9F47-4FFE-AD06-2B74CB89E76B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample03", "src\samples\WorkflowCore.Sample03\WorkflowCore.Sample03.csproj", "{FB738255-0304-4A25-B256-22E36EDF9507}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample03", "src\samples\WorkflowCore.Sample03\WorkflowCore.Sample03.csproj", "{FB738255-0304-4A25-B256-22E36EDF9507}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample04", "src\samples\WorkflowCore.Sample04\WorkflowCore.Sample04.csproj", "{91301F52-E589-499E-97DE-91FA074B790C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample04", "src\samples\WorkflowCore.Sample04\WorkflowCore.Sample04.csproj", "{91301F52-E589-499E-97DE-91FA074B790C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample05", "src\samples\WorkflowCore.Sample05\WorkflowCore.Sample05.csproj", "{68883A5C-BD59-404D-A394-18104D6F472C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample05", "src\samples\WorkflowCore.Sample05\WorkflowCore.Sample05.csproj", "{68883A5C-BD59-404D-A394-18104D6F472C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Persistence.EntityFramework", "src\providers\WorkflowCore.Persistence.EntityFramework\WorkflowCore.Persistence.EntityFramework.csproj", "{FE54AD67-817A-4CC6-A9EF-C9F7A5122CA4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Persistence.EntityFramework", "src\providers\WorkflowCore.Persistence.EntityFramework\WorkflowCore.Persistence.EntityFramework.csproj", "{FE54AD67-817A-4CC6-A9EF-C9F7A5122CA4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Persistence.SqlServer", "src\providers\WorkflowCore.Persistence.SqlServer\WorkflowCore.Persistence.SqlServer.csproj", "{1DE96D4F-F2CA-4740-8764-BADD1000040A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Persistence.SqlServer", "src\providers\WorkflowCore.Persistence.SqlServer\WorkflowCore.Persistence.SqlServer.csproj", "{1DE96D4F-F2CA-4740-8764-BADD1000040A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Persistence.PostgreSQL", "src\providers\WorkflowCore.Persistence.PostgreSQL\WorkflowCore.Persistence.PostgreSQL.csproj", "{9274B938-3996-4FBA-AE2F-0C82009B1116}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Persistence.PostgreSQL", "src\providers\WorkflowCore.Persistence.PostgreSQL\WorkflowCore.Persistence.PostgreSQL.csproj", "{9274B938-3996-4FBA-AE2F-0C82009B1116}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Persistence.Sqlite", "src\providers\WorkflowCore.Persistence.Sqlite\WorkflowCore.Persistence.Sqlite.csproj", "{86BC1E05-E9CE-4E53-B324-885A2FDBCE74}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Persistence.Sqlite", "src\providers\WorkflowCore.Persistence.Sqlite\WorkflowCore.Persistence.Sqlite.csproj", "{86BC1E05-E9CE-4E53-B324-885A2FDBCE74}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.LockProviders.Redlock", "src\providers\WorkflowCore.LockProviders.Redlock\WorkflowCore.LockProviders.Redlock.csproj", "{05250D58-A59E-4212-8D55-E7BC0396E9F5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.LockProviders.Redlock", "src\providers\WorkflowCore.LockProviders.Redlock\WorkflowCore.LockProviders.Redlock.csproj", "{05250D58-A59E-4212-8D55-E7BC0396E9F5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.QueueProviders.RabbitMQ", "src\providers\WorkflowCore.QueueProviders.RabbitMQ\WorkflowCore.QueueProviders.RabbitMQ.csproj", "{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.QueueProviders.RabbitMQ", "src\providers\WorkflowCore.QueueProviders.RabbitMQ\WorkflowCore.QueueProviders.RabbitMQ.csproj", "{AFAD87C7-B2EE-451E-BA7E-3F5A91358C48}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample06", "src\samples\WorkflowCore.Sample06\WorkflowCore.Sample06.csproj", "{8FEAFD74-C304-4F75-BA38-4686BE55C891}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample06", "src\samples\WorkflowCore.Sample06\WorkflowCore.Sample06.csproj", "{8FEAFD74-C304-4F75-BA38-4686BE55C891}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.UnitTests", "test\WorkflowCore.UnitTests\WorkflowCore.UnitTests.csproj", "{37B598A8-B054-4ABA-884D-96AEF2511600}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.UnitTests", "test\WorkflowCore.UnitTests\WorkflowCore.UnitTests.csproj", "{37B598A8-B054-4ABA-884D-96AEF2511600}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.TestAssets", "test\WorkflowCore.TestAssets\WorkflowCore.TestAssets.csproj", "{17C270A8-EC88-4883-9318-74BB28EFF508}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.TestAssets", "test\WorkflowCore.TestAssets\WorkflowCore.TestAssets.csproj", "{17C270A8-EC88-4883-9318-74BB28EFF508}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample07", "src\samples\WorkflowCore.Sample07\WorkflowCore.Sample07.csproj", "{0631B4BA-D5DD-4C9E-8842-0D370A3D714A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample07", "src\samples\WorkflowCore.Sample07\WorkflowCore.Sample07.csproj", "{0631B4BA-D5DD-4C9E-8842-0D370A3D714A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.QueueProviders.ZeroMQ", "src\providers\WorkflowCore.QueueProviders.ZeroMQ\WorkflowCore.QueueProviders.ZeroMQ.csproj", "{F33E89F6-BC8A-4E11-BEA1-C9A80E815DE8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.QueueProviders.ZeroMQ", "src\providers\WorkflowCore.QueueProviders.ZeroMQ\WorkflowCore.QueueProviders.ZeroMQ.csproj", "{F33E89F6-BC8A-4E11-BEA1-C9A80E815DE8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.LockProviders.ZeroMQ", "src\providers\WorkflowCore.LockProviders.ZeroMQ\WorkflowCore.LockProviders.ZeroMQ.csproj", "{4DC2200D-BA4F-4508-8426-F0CAE5EE90B5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.LockProviders.ZeroMQ", "src\providers\WorkflowCore.LockProviders.ZeroMQ\WorkflowCore.LockProviders.ZeroMQ.csproj", "{4DC2200D-BA4F-4508-8426-F0CAE5EE90B5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Tests.ZeroMQ", "test\WorkflowCore.Tests.ZeroMQ\WorkflowCore.Tests.ZeroMQ.csproj", "{15FB5CB9-075B-4A54-98EC-2A1F5C40BE48}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Tests.ZeroMQ", "test\WorkflowCore.Tests.ZeroMQ\WorkflowCore.Tests.ZeroMQ.csproj", "{15FB5CB9-075B-4A54-98EC-2A1F5C40BE48}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScratchPad", "test\ScratchPad\ScratchPad.csproj", "{6B1C4B1E-AB43-4E87-A137-3AD9F534F059}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScratchPad", "test\ScratchPad\ScratchPad.csproj", "{6B1C4B1E-AB43-4E87-A137-3AD9F534F059}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.IntegrationTests", "test\WorkflowCore.IntegrationTests\WorkflowCore.IntegrationTests.csproj", "{9162B6AD-AD06-4C64-9032-0E727643B1B9}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.IntegrationTests", "test\WorkflowCore.IntegrationTests\WorkflowCore.IntegrationTests.csproj", "{9162B6AD-AD06-4C64-9032-0E727643B1B9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Tests.MongoDB", "test\WorkflowCore.Tests.MongoDB\WorkflowCore.Tests.MongoDB.csproj", "{58EC09C7-EC0A-4708-9B6F-FBE6243CEB49}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Tests.MongoDB", "test\WorkflowCore.Tests.MongoDB\WorkflowCore.Tests.MongoDB.csproj", "{58EC09C7-EC0A-4708-9B6F-FBE6243CEB49}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Tests.PostgreSQL", "test\WorkflowCore.Tests.PostgreSQL\WorkflowCore.Tests.PostgreSQL.csproj", "{8C2BD4D2-43EC-4930-9364-CDA938C01803}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Tests.PostgreSQL", "test\WorkflowCore.Tests.PostgreSQL\WorkflowCore.Tests.PostgreSQL.csproj", "{8C2BD4D2-43EC-4930-9364-CDA938C01803}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Users", "src\extensions\WorkflowCore.Users\WorkflowCore.Users.csproj", "{4C4DE624-9D91-484F-8BF7-2D71264EAB8B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Users", "src\extensions\WorkflowCore.Users\WorkflowCore.Users.csproj", "{4C4DE624-9D91-484F-8BF7-2D71264EAB8B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample08", "src\samples\WorkflowCore.Sample08\WorkflowCore.Sample08.csproj", "{ED5074AF-A09E-4357-A419-FE3476C0FAE7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample08", "src\samples\WorkflowCore.Sample08\WorkflowCore.Sample08.csproj", "{ED5074AF-A09E-4357-A419-FE3476C0FAE7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.WebAPI", "src\extensions\WorkflowCore.WebAPI\WorkflowCore.WebAPI.csproj", "{FBF8D151-A3BF-4EB3-8F80-D71618696362}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.WebAPI", "src\extensions\WorkflowCore.WebAPI\WorkflowCore.WebAPI.csproj", "{FBF8D151-A3BF-4EB3-8F80-D71618696362}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample09", "src\samples\WorkflowCore.Sample09\WorkflowCore.Sample09.csproj", "{50E1AFAC-0B58-43A8-8F03-3A63AAC681FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample09", "src\samples\WorkflowCore.Sample09\WorkflowCore.Sample09.csproj", "{50E1AFAC-0B58-43A8-8F03-3A63AAC681FA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample10", "src\samples\WorkflowCore.Sample10\WorkflowCore.Sample10.csproj", "{5E792455-4C4C-460F-849E-50A5DCED454D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample10", "src\samples\WorkflowCore.Sample10\WorkflowCore.Sample10.csproj", "{5E792455-4C4C-460F-849E-50A5DCED454D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample11", "src\samples\WorkflowCore.Sample11\WorkflowCore.Sample11.csproj", "{58D0480F-D05D-4348-86D9-B0A7255700E6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample11", "src\samples\WorkflowCore.Sample11\WorkflowCore.Sample11.csproj", "{58D0480F-D05D-4348-86D9-B0A7255700E6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample12", "src\samples\WorkflowCore.Sample12\WorkflowCore.Sample12.csproj", "{BB776411-D279-419F-8697-5C6F52BCD5CD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample12", "src\samples\WorkflowCore.Sample12\WorkflowCore.Sample12.csproj", "{BB776411-D279-419F-8697-5C6F52BCD5CD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Tests.Sqlite", "test\WorkflowCore.Tests.Sqlite\WorkflowCore.Tests.Sqlite.csproj", "{F9F8F9CD-01D9-468B-856D-6A87F0762A01}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Tests.Sqlite", "test\WorkflowCore.Tests.Sqlite\WorkflowCore.Tests.Sqlite.csproj", "{F9F8F9CD-01D9-468B-856D-6A87F0762A01}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Tests.SqlServer", "test\WorkflowCore.Tests.SqlServer\WorkflowCore.Tests.SqlServer.csproj", "{A4B8E54D-F8FE-4358-AE14-A5354E828938}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Tests.SqlServer", "test\WorkflowCore.Tests.SqlServer\WorkflowCore.Tests.SqlServer.csproj", "{A4B8E54D-F8FE-4358-AE14-A5354E828938}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.LockProviders.SqlServer", "src\providers\WorkflowCore.LockProviders.SqlServer\WorkflowCore.LockProviders.SqlServer.csproj", "{AAE2E9F9-37EF-4AE1-A200-D37417C9040C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.LockProviders.SqlServer", "src\providers\WorkflowCore.LockProviders.SqlServer\WorkflowCore.LockProviders.SqlServer.csproj", "{AAE2E9F9-37EF-4AE1-A200-D37417C9040C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample13", "src\samples\WorkflowCore.Sample13\WorkflowCore.Sample13.csproj", "{77C49ACA-203E-428C-A4DB-114DFE454988}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample13", "src\samples\WorkflowCore.Sample13\WorkflowCore.Sample13.csproj", "{77C49ACA-203E-428C-A4DB-114DFE454988}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Providers.Azure", "src\providers\WorkflowCore.Providers.Azure\WorkflowCore.Providers.Azure.csproj", "{A2374B7C-4198-40B3-B8FE-FAC3DB3F2539}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Providers.Azure", "src\providers\WorkflowCore.Providers.Azure\WorkflowCore.Providers.Azure.csproj", "{A2374B7C-4198-40B3-B8FE-FAC3DB3F2539}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReleaseNotes", "ReleaseNotes", "{38ECB00C-3F3B-4442-8408-ACE3B37FFAA8}" ProjectSection(SolutionItems) = preProject ReleaseNotes\1.2.8.md = ReleaseNotes\1.2.8.md ReleaseNotes\1.2.9.md = ReleaseNotes\1.2.9.md + ReleaseNotes\1.3.0.md = ReleaseNotes\1.3.0.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Sample14", "src\samples\WorkflowCore.Sample14\WorkflowCore.Sample14.csproj", "{6BC66637-B42A-4334-ADFB-DBEC9F29D293}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Sample14", "src\samples\WorkflowCore.Sample14\WorkflowCore.Sample14.csproj", "{6BC66637-B42A-4334-ADFB-DBEC9F29D293}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.Testing", "test\WorkflowCore.Testing\WorkflowCore.Testing.csproj", "{62A9709E-27DA-42EE-B94F-5AF431D86354}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.Testing", "test\WorkflowCore.Testing\WorkflowCore.Testing.csproj", "{62A9709E-27DA-42EE-B94F-5AF431D86354}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkflowCore.TestSample01", "src\samples\WorkflowCore.TestSample01\WorkflowCore.TestSample01.csproj", "{0E3C1496-8E7C-411A-A536-C7C9CE4EED4E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkflowCore.TestSample01", "src\samples\WorkflowCore.TestSample01\WorkflowCore.TestSample01.csproj", "{0E3C1496-8E7C-411A-A536-C7C9CE4EED4E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docker.Testify", "test\Docker.Testify\Docker.Testify.csproj", "{EC497168-5347-4E70-9D9E-9C2F826C1CDF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Docker.Testify", "test\Docker.Testify\Docker.Testify.csproj", "{EC497168-5347-4E70-9D9E-9C2F826C1CDF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/WorkflowCore/Models/WorkflowInstance.cs b/src/WorkflowCore/Models/WorkflowInstance.cs index 42a2846ca..3fb70bf44 100644 --- a/src/WorkflowCore/Models/WorkflowInstance.cs +++ b/src/WorkflowCore/Models/WorkflowInstance.cs @@ -16,6 +16,8 @@ public class WorkflowInstance public string Description { get; set; } + public string Reference { get; set; } + public List ExecutionPointers { get; set; } = new List(); public long? NextExecution { get; set; } diff --git a/src/extensions/WorkflowCore.WebAPI/WorkflowCore.WebAPI.csproj b/src/extensions/WorkflowCore.WebAPI/WorkflowCore.WebAPI.csproj index a996ce048..188b56b9e 100644 --- a/src/extensions/WorkflowCore.WebAPI/WorkflowCore.WebAPI.csproj +++ b/src/extensions/WorkflowCore.WebAPI/WorkflowCore.WebAPI.csproj @@ -26,8 +26,8 @@ - - + + diff --git a/src/providers/WorkflowCore.Persistence.EntityFramework/ExtensionMethods.cs b/src/providers/WorkflowCore.Persistence.EntityFramework/ExtensionMethods.cs index 05515f7de..936999f6d 100644 --- a/src/providers/WorkflowCore.Persistence.EntityFramework/ExtensionMethods.cs +++ b/src/providers/WorkflowCore.Persistence.EntityFramework/ExtensionMethods.cs @@ -19,6 +19,7 @@ internal static PersistedWorkflow ToPersistable(this WorkflowInstance instance, persistable.Data = JsonConvert.SerializeObject(instance.Data, SerializerSettings); persistable.Description = instance.Description; + persistable.Reference = instance.Reference; persistable.InstanceId = new Guid(instance.Id); persistable.NextExecution = instance.NextExecution; persistable.Version = instance.Version; @@ -118,6 +119,7 @@ internal static WorkflowInstance ToWorkflowInstance(this PersistedWorkflow insta WorkflowInstance result = new WorkflowInstance(); result.Data = JsonConvert.DeserializeObject(instance.Data, SerializerSettings); result.Description = instance.Description; + result.Reference = instance.Reference; result.Id = instance.InstanceId.ToString(); result.NextExecution = instance.NextExecution; result.Version = instance.Version; diff --git a/src/providers/WorkflowCore.Persistence.EntityFramework/Models/PersistedWorkflow.cs b/src/providers/WorkflowCore.Persistence.EntityFramework/Models/PersistedWorkflow.cs index 07adb8afd..626e2661f 100644 --- a/src/providers/WorkflowCore.Persistence.EntityFramework/Models/PersistedWorkflow.cs +++ b/src/providers/WorkflowCore.Persistence.EntityFramework/Models/PersistedWorkflow.cs @@ -24,6 +24,9 @@ public class PersistedWorkflow [MaxLength(500)] public string Description { get; set; } + [MaxLength(200)] + public string Reference { get; set; } + public virtual List ExecutionPointers { get; set; } = new List(); //[Index] diff --git a/src/providers/WorkflowCore.Persistence.EntityFramework/WorkflowCore.Persistence.EntityFramework.csproj b/src/providers/WorkflowCore.Persistence.EntityFramework/WorkflowCore.Persistence.EntityFramework.csproj index 0670758a6..e08ebe191 100644 --- a/src/providers/WorkflowCore.Persistence.EntityFramework/WorkflowCore.Persistence.EntityFramework.csproj +++ b/src/providers/WorkflowCore.Persistence.EntityFramework/WorkflowCore.Persistence.EntityFramework.csproj @@ -28,9 +28,9 @@ - - - + + + diff --git a/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs b/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs index c4934bf55..aa3a6b3cc 100644 --- a/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs +++ b/src/providers/WorkflowCore.Persistence.MongoDB/Services/MongoPersistenceProvider.cs @@ -37,6 +37,7 @@ static MongoPersistenceProvider() x.MapProperty(y => y.Data) .SetSerializer(new DataObjectSerializer()); x.MapProperty(y => y.Description); + x.MapProperty(y => y.Reference); x.MapProperty(y => y.WorkflowDefinitionId); x.MapProperty(y => y.Version); x.MapProperty(y => y.NextExecution); diff --git a/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj b/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj index 2d88ac50e..f5dad14c4 100644 --- a/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj +++ b/src/providers/WorkflowCore.Persistence.MongoDB/WorkflowCore.Persistence.MongoDB.csproj @@ -28,8 +28,8 @@ - - + + diff --git a/src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/20170722200412_WfReference.Designer.cs b/src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/20170722200412_WfReference.Designer.cs new file mode 100644 index 000000000..945760e73 --- /dev/null +++ b/src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/20170722200412_WfReference.Designer.cs @@ -0,0 +1,259 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using WorkflowCore.Persistence.PostgreSQL; +using WorkflowCore.Models; + +namespace WorkflowCore.Persistence.PostgreSQL.Migrations +{ + [DbContext(typeof(PostgresPersistenceProvider))] + [Migration("20170722200412_WfReference")] + partial class WfReference + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .HasAnnotation("ProductVersion", "1.1.2"); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedEvent", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); + + b.Property("EventData"); + + b.Property("EventId"); + + b.Property("EventKey") + .HasMaxLength(200); + + b.Property("EventName") + .HasMaxLength(200); + + b.Property("EventTime"); + + b.Property("IsProcessed"); + + b.HasKey("PersistenceId"); + + b.HasIndex("EventId") + .IsUnique(); + + b.HasIndex("EventTime"); + + b.HasIndex("IsProcessed"); + + b.HasIndex("EventName", "EventKey"); + + b.ToTable("PersistedEvent"); + + b.HasAnnotation("Npgsql:Schema", "wfc"); + + b.HasAnnotation("Npgsql:TableName", "Event"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionError", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); + + b.Property("ErrorTime"); + + b.Property("ExecutionPointerId") + .HasMaxLength(100); + + b.Property("Message"); + + b.Property("WorkflowId") + .HasMaxLength(100); + + b.HasKey("PersistenceId"); + + b.ToTable("PersistedExecutionError"); + + b.HasAnnotation("Npgsql:Schema", "wfc"); + + b.HasAnnotation("Npgsql:TableName", "ExecutionError"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionPointer", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); + + b.Property("Active"); + + b.Property("Children"); + + b.Property("ContextItem"); + + b.Property("EndTime"); + + b.Property("EventData"); + + b.Property("EventKey") + .HasMaxLength(100); + + b.Property("EventName") + .HasMaxLength(100); + + b.Property("EventPublished"); + + b.Property("Id") + .HasMaxLength(50); + + b.Property("Outcome"); + + b.Property("PersistenceData"); + + b.Property("PredecessorId") + .HasMaxLength(100); + + b.Property("RetryCount"); + + b.Property("SleepUntil"); + + b.Property("StartTime"); + + b.Property("StepId"); + + b.Property("StepName") + .HasMaxLength(100); + + b.Property("WorkflowId"); + + b.HasKey("PersistenceId"); + + b.HasIndex("WorkflowId"); + + b.ToTable("PersistedExecutionPointer"); + + b.HasAnnotation("Npgsql:Schema", "wfc"); + + b.HasAnnotation("Npgsql:TableName", "ExecutionPointer"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExtensionAttribute", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); + + b.Property("AttributeKey") + .HasMaxLength(100); + + b.Property("AttributeValue"); + + b.Property("ExecutionPointerId"); + + b.HasKey("PersistenceId"); + + b.HasIndex("ExecutionPointerId"); + + b.ToTable("PersistedExtensionAttribute"); + + b.HasAnnotation("Npgsql:Schema", "wfc"); + + b.HasAnnotation("Npgsql:TableName", "ExtensionAttribute"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedSubscription", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); + + b.Property("EventKey") + .HasMaxLength(200); + + b.Property("EventName") + .HasMaxLength(200); + + b.Property("StepId"); + + b.Property("SubscribeAsOf"); + + b.Property("SubscriptionId") + .HasMaxLength(200); + + b.Property("WorkflowId") + .HasMaxLength(200); + + b.HasKey("PersistenceId"); + + b.HasIndex("EventKey"); + + b.HasIndex("EventName"); + + b.HasIndex("SubscriptionId") + .IsUnique(); + + b.ToTable("PersistedSubscription"); + + b.HasAnnotation("Npgsql:Schema", "wfc"); + + b.HasAnnotation("Npgsql:TableName", "Subscription"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedWorkflow", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); + + b.Property("CompleteTime"); + + b.Property("CreateTime"); + + b.Property("Data"); + + b.Property("Description") + .HasMaxLength(500); + + b.Property("InstanceId") + .HasMaxLength(200); + + b.Property("NextExecution"); + + b.Property("Reference") + .HasMaxLength(200); + + b.Property("Status"); + + b.Property("Version"); + + b.Property("WorkflowDefinitionId") + .HasMaxLength(200); + + b.HasKey("PersistenceId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.HasIndex("NextExecution"); + + b.ToTable("PersistedWorkflow"); + + b.HasAnnotation("Npgsql:Schema", "wfc"); + + b.HasAnnotation("Npgsql:TableName", "Workflow"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionPointer", b => + { + b.HasOne("WorkflowCore.Persistence.EntityFramework.Models.PersistedWorkflow", "Workflow") + .WithMany("ExecutionPointers") + .HasForeignKey("WorkflowId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExtensionAttribute", b => + { + b.HasOne("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionPointer", "ExecutionPointer") + .WithMany("ExtensionAttributes") + .HasForeignKey("ExecutionPointerId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/20170722200412_WfReference.cs b/src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/20170722200412_WfReference.cs new file mode 100644 index 000000000..997299d80 --- /dev/null +++ b/src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/20170722200412_WfReference.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace WorkflowCore.Persistence.PostgreSQL.Migrations +{ + public partial class WfReference : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Reference", + schema: "wfc", + table: "Workflow", + maxLength: 200, + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Reference", + schema: "wfc", + table: "Workflow"); + } + } +} diff --git a/src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/PostgresPersistenceProviderModelSnapshot.cs b/src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/PostgresPersistenceProviderModelSnapshot.cs index bb2338931..70df92086 100644 --- a/src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/PostgresPersistenceProviderModelSnapshot.cs +++ b/src/providers/WorkflowCore.Persistence.PostgreSQL/Migrations/PostgresPersistenceProviderModelSnapshot.cs @@ -14,248 +14,245 @@ partial class PostgresPersistenceProviderModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder - .HasAnnotation("ProductVersion", "1.1.1") - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn); + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) + .HasAnnotation("ProductVersion", "1.1.2"); modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedEvent", b => - { - b.Property("PersistenceId") - .ValueGeneratedOnAdd() - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn); + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); - b.Property("EventData"); + b.Property("EventData"); - b.Property("EventId"); + b.Property("EventId"); - b.Property("EventKey") - .HasMaxLength(200); + b.Property("EventKey") + .HasMaxLength(200); - b.Property("EventName") - .HasMaxLength(200); + b.Property("EventName") + .HasMaxLength(200); - b.Property("EventTime"); + b.Property("EventTime"); - b.Property("IsProcessed"); + b.Property("IsProcessed"); - b.HasKey("PersistenceId"); + b.HasKey("PersistenceId"); - b.HasIndex("EventId") - .IsUnique(); + b.HasIndex("EventId") + .IsUnique(); - b.HasIndex("EventTime"); + b.HasIndex("EventTime"); - b.HasIndex("IsProcessed"); + b.HasIndex("IsProcessed"); - b.HasIndex("EventName", "EventKey"); + b.HasIndex("EventName", "EventKey"); - b.ToTable("PersistedEvent"); + b.ToTable("PersistedEvent"); - b.HasAnnotation("Npgsql:Schema", "wfc"); + b.HasAnnotation("Npgsql:Schema", "wfc"); - b.HasAnnotation("Npgsql:TableName", "Event"); - }); + b.HasAnnotation("Npgsql:TableName", "Event"); + }); modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionError", b => - { - b.Property("PersistenceId") - .ValueGeneratedOnAdd() - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn); + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); - b.Property("ErrorTime"); + b.Property("ErrorTime"); - b.Property("ExecutionPointerId") - .HasMaxLength(100); + b.Property("ExecutionPointerId") + .HasMaxLength(100); - b.Property("Message"); + b.Property("Message"); - b.Property("WorkflowId") - .HasMaxLength(100); + b.Property("WorkflowId") + .HasMaxLength(100); - b.HasKey("PersistenceId"); + b.HasKey("PersistenceId"); - b.ToTable("PersistedExecutionError"); + b.ToTable("PersistedExecutionError"); - b.HasAnnotation("Npgsql:Schema", "wfc"); + b.HasAnnotation("Npgsql:Schema", "wfc"); - b.HasAnnotation("Npgsql:TableName", "ExecutionError"); - }); + b.HasAnnotation("Npgsql:TableName", "ExecutionError"); + }); modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionPointer", b => - { - b.Property("PersistenceId") - .ValueGeneratedOnAdd() - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn); + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); - b.Property("Active"); + b.Property("Active"); - b.Property("Children"); + b.Property("Children"); - b.Property("ContextItem"); + b.Property("ContextItem"); - b.Property("EndTime"); + b.Property("EndTime"); - b.Property("EventData"); + b.Property("EventData"); - b.Property("EventKey") - .HasMaxLength(100); + b.Property("EventKey") + .HasMaxLength(100); - b.Property("EventName") - .HasMaxLength(100); + b.Property("EventName") + .HasMaxLength(100); - b.Property("EventPublished"); + b.Property("EventPublished"); - b.Property("Id") - .HasMaxLength(50); + b.Property("Id") + .HasMaxLength(50); - b.Property("Outcome"); + b.Property("Outcome"); - b.Property("PersistenceData"); + b.Property("PersistenceData"); - b.Property("PredecessorId") - .HasMaxLength(100); + b.Property("PredecessorId") + .HasMaxLength(100); - b.Property("RetryCount"); + b.Property("RetryCount"); - b.Property("SleepUntil"); + b.Property("SleepUntil"); - b.Property("StartTime"); + b.Property("StartTime"); - b.Property("StepId"); + b.Property("StepId"); - b.Property("StepName") - .HasMaxLength(100); + b.Property("StepName") + .HasMaxLength(100); - b.Property("WorkflowId"); + b.Property("WorkflowId"); - b.HasKey("PersistenceId"); + b.HasKey("PersistenceId"); - b.HasIndex("WorkflowId"); + b.HasIndex("WorkflowId"); - b.ToTable("PersistedExecutionPointer"); + b.ToTable("PersistedExecutionPointer"); - b.HasAnnotation("Npgsql:Schema", "wfc"); + b.HasAnnotation("Npgsql:Schema", "wfc"); - b.HasAnnotation("Npgsql:TableName", "ExecutionPointer"); - }); + b.HasAnnotation("Npgsql:TableName", "ExecutionPointer"); + }); modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExtensionAttribute", b => - { - b.Property("PersistenceId") - .ValueGeneratedOnAdd() - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn); + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); - b.Property("AttributeKey") - .HasMaxLength(100); + b.Property("AttributeKey") + .HasMaxLength(100); - b.Property("AttributeValue"); + b.Property("AttributeValue"); - b.Property("ExecutionPointerId"); + b.Property("ExecutionPointerId"); - b.HasKey("PersistenceId"); + b.HasKey("PersistenceId"); - b.HasIndex("ExecutionPointerId"); + b.HasIndex("ExecutionPointerId"); - b.ToTable("PersistedExtensionAttribute"); + b.ToTable("PersistedExtensionAttribute"); - b.HasAnnotation("Npgsql:Schema", "wfc"); + b.HasAnnotation("Npgsql:Schema", "wfc"); - b.HasAnnotation("Npgsql:TableName", "ExtensionAttribute"); - }); + b.HasAnnotation("Npgsql:TableName", "ExtensionAttribute"); + }); modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedSubscription", b => - { - b.Property("PersistenceId") - .ValueGeneratedOnAdd() - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn); + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); - b.Property("EventKey") - .HasMaxLength(200); + b.Property("EventKey") + .HasMaxLength(200); - b.Property("EventName") - .HasMaxLength(200); + b.Property("EventName") + .HasMaxLength(200); - b.Property("StepId"); + b.Property("StepId"); - b.Property("SubscribeAsOf"); + b.Property("SubscribeAsOf"); - b.Property("SubscriptionId") - .HasMaxLength(200); + b.Property("SubscriptionId") + .HasMaxLength(200); - b.Property("WorkflowId") - .HasMaxLength(200); + b.Property("WorkflowId") + .HasMaxLength(200); - b.HasKey("PersistenceId"); + b.HasKey("PersistenceId"); - b.HasIndex("EventKey"); + b.HasIndex("EventKey"); - b.HasIndex("EventName"); + b.HasIndex("EventName"); - b.HasIndex("SubscriptionId") - .IsUnique(); + b.HasIndex("SubscriptionId") + .IsUnique(); - b.ToTable("PersistedSubscription"); + b.ToTable("PersistedSubscription"); - b.HasAnnotation("Npgsql:Schema", "wfc"); + b.HasAnnotation("Npgsql:Schema", "wfc"); - b.HasAnnotation("Npgsql:TableName", "Subscription"); - }); + b.HasAnnotation("Npgsql:TableName", "Subscription"); + }); modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedWorkflow", b => - { - b.Property("PersistenceId") - .ValueGeneratedOnAdd() - .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn); + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd(); - b.Property("CompleteTime"); + b.Property("CompleteTime"); - b.Property("CreateTime"); + b.Property("CreateTime"); - b.Property("Data"); + b.Property("Data"); - b.Property("Description") - .HasMaxLength(500); + b.Property("Description") + .HasMaxLength(500); - b.Property("InstanceId") - .HasMaxLength(200); + b.Property("InstanceId") + .HasMaxLength(200); - b.Property("NextExecution"); + b.Property("NextExecution"); - b.Property("Status"); + b.Property("Reference") + .HasMaxLength(200); - b.Property("Version"); + b.Property("Status"); - b.Property("WorkflowDefinitionId") - .HasMaxLength(200); + b.Property("Version"); - b.HasKey("PersistenceId"); + b.Property("WorkflowDefinitionId") + .HasMaxLength(200); - b.HasIndex("InstanceId") - .IsUnique(); + b.HasKey("PersistenceId"); - b.HasIndex("NextExecution"); + b.HasIndex("InstanceId") + .IsUnique(); - b.ToTable("PersistedWorkflow"); + b.HasIndex("NextExecution"); - b.HasAnnotation("Npgsql:Schema", "wfc"); + b.ToTable("PersistedWorkflow"); - b.HasAnnotation("Npgsql:TableName", "Workflow"); - }); + b.HasAnnotation("Npgsql:Schema", "wfc"); + + b.HasAnnotation("Npgsql:TableName", "Workflow"); + }); modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionPointer", b => - { - b.HasOne("WorkflowCore.Persistence.EntityFramework.Models.PersistedWorkflow", "Workflow") - .WithMany("ExecutionPointers") - .HasForeignKey("WorkflowId") - .OnDelete(DeleteBehavior.Cascade); - }); + { + b.HasOne("WorkflowCore.Persistence.EntityFramework.Models.PersistedWorkflow", "Workflow") + .WithMany("ExecutionPointers") + .HasForeignKey("WorkflowId") + .OnDelete(DeleteBehavior.Cascade); + }); modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExtensionAttribute", b => - { - b.HasOne("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionPointer", "ExecutionPointer") - .WithMany("ExtensionAttributes") - .HasForeignKey("ExecutionPointerId") - .OnDelete(DeleteBehavior.Cascade); - }); + { + b.HasOne("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionPointer", "ExecutionPointer") + .WithMany("ExtensionAttributes") + .HasForeignKey("ExecutionPointerId") + .OnDelete(DeleteBehavior.Cascade); + }); } } } diff --git a/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj b/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj index c590229b0..da0bc7e4a 100644 --- a/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj +++ b/src/providers/WorkflowCore.Persistence.PostgreSQL/WorkflowCore.Persistence.PostgreSQL.csproj @@ -29,12 +29,12 @@ - + All - + diff --git a/src/providers/WorkflowCore.Persistence.SqlServer/Migrations/20170722195832_WfReference.Designer.cs b/src/providers/WorkflowCore.Persistence.SqlServer/Migrations/20170722195832_WfReference.Designer.cs new file mode 100644 index 000000000..380fb7a2c --- /dev/null +++ b/src/providers/WorkflowCore.Persistence.SqlServer/Migrations/20170722195832_WfReference.Designer.cs @@ -0,0 +1,265 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using WorkflowCore.Persistence.SqlServer; +using WorkflowCore.Models; + +namespace WorkflowCore.Persistence.SqlServer.Migrations +{ + [DbContext(typeof(SqlServerPersistenceProvider))] + [Migration("20170722195832_WfReference")] + partial class WfReference + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.2") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedEvent", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("EventData"); + + b.Property("EventId"); + + b.Property("EventKey") + .HasMaxLength(200); + + b.Property("EventName") + .HasMaxLength(200); + + b.Property("EventTime"); + + b.Property("IsProcessed"); + + b.HasKey("PersistenceId"); + + b.HasIndex("EventId") + .IsUnique(); + + b.HasIndex("EventTime"); + + b.HasIndex("IsProcessed"); + + b.HasIndex("EventName", "EventKey"); + + b.ToTable("PersistedEvent"); + + b.HasAnnotation("SqlServer:Schema", "wfc"); + + b.HasAnnotation("SqlServer:TableName", "Event"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionError", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ErrorTime"); + + b.Property("ExecutionPointerId") + .HasMaxLength(100); + + b.Property("Message"); + + b.Property("WorkflowId") + .HasMaxLength(100); + + b.HasKey("PersistenceId"); + + b.ToTable("PersistedExecutionError"); + + b.HasAnnotation("SqlServer:Schema", "wfc"); + + b.HasAnnotation("SqlServer:TableName", "ExecutionError"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionPointer", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Active"); + + b.Property("Children"); + + b.Property("ContextItem"); + + b.Property("EndTime"); + + b.Property("EventData"); + + b.Property("EventKey") + .HasMaxLength(100); + + b.Property("EventName") + .HasMaxLength(100); + + b.Property("EventPublished"); + + b.Property("Id") + .HasMaxLength(50); + + b.Property("Outcome"); + + b.Property("PersistenceData"); + + b.Property("PredecessorId") + .HasMaxLength(100); + + b.Property("RetryCount"); + + b.Property("SleepUntil"); + + b.Property("StartTime"); + + b.Property("StepId"); + + b.Property("StepName") + .HasMaxLength(100); + + b.Property("WorkflowId"); + + b.HasKey("PersistenceId"); + + b.HasIndex("WorkflowId"); + + b.ToTable("PersistedExecutionPointer"); + + b.HasAnnotation("SqlServer:Schema", "wfc"); + + b.HasAnnotation("SqlServer:TableName", "ExecutionPointer"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExtensionAttribute", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AttributeKey") + .HasMaxLength(100); + + b.Property("AttributeValue"); + + b.Property("ExecutionPointerId"); + + b.HasKey("PersistenceId"); + + b.HasIndex("ExecutionPointerId"); + + b.ToTable("PersistedExtensionAttribute"); + + b.HasAnnotation("SqlServer:Schema", "wfc"); + + b.HasAnnotation("SqlServer:TableName", "ExtensionAttribute"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedSubscription", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("EventKey") + .HasMaxLength(200); + + b.Property("EventName") + .HasMaxLength(200); + + b.Property("StepId"); + + b.Property("SubscribeAsOf"); + + b.Property("SubscriptionId") + .HasMaxLength(200); + + b.Property("WorkflowId") + .HasMaxLength(200); + + b.HasKey("PersistenceId"); + + b.HasIndex("EventKey"); + + b.HasIndex("EventName"); + + b.HasIndex("SubscriptionId") + .IsUnique(); + + b.ToTable("PersistedSubscription"); + + b.HasAnnotation("SqlServer:Schema", "wfc"); + + b.HasAnnotation("SqlServer:TableName", "Subscription"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedWorkflow", b => + { + b.Property("PersistenceId") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CompleteTime"); + + b.Property("CreateTime"); + + b.Property("Data"); + + b.Property("Description") + .HasMaxLength(500); + + b.Property("InstanceId") + .HasMaxLength(200); + + b.Property("NextExecution"); + + b.Property("Reference") + .HasMaxLength(200); + + b.Property("Status"); + + b.Property("Version"); + + b.Property("WorkflowDefinitionId") + .HasMaxLength(200); + + b.HasKey("PersistenceId"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.HasIndex("NextExecution"); + + b.ToTable("PersistedWorkflow"); + + b.HasAnnotation("SqlServer:Schema", "wfc"); + + b.HasAnnotation("SqlServer:TableName", "Workflow"); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionPointer", b => + { + b.HasOne("WorkflowCore.Persistence.EntityFramework.Models.PersistedWorkflow", "Workflow") + .WithMany("ExecutionPointers") + .HasForeignKey("WorkflowId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedExtensionAttribute", b => + { + b.HasOne("WorkflowCore.Persistence.EntityFramework.Models.PersistedExecutionPointer", "ExecutionPointer") + .WithMany("ExtensionAttributes") + .HasForeignKey("ExecutionPointerId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/providers/WorkflowCore.Persistence.SqlServer/Migrations/20170722195832_WfReference.cs b/src/providers/WorkflowCore.Persistence.SqlServer/Migrations/20170722195832_WfReference.cs new file mode 100644 index 000000000..1e94cad6d --- /dev/null +++ b/src/providers/WorkflowCore.Persistence.SqlServer/Migrations/20170722195832_WfReference.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace WorkflowCore.Persistence.SqlServer.Migrations +{ + public partial class WfReference : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Reference", + schema: "wfc", + table: "Workflow", + maxLength: 200, + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Reference", + schema: "wfc", + table: "Workflow"); + } + } +} diff --git a/src/providers/WorkflowCore.Persistence.SqlServer/Migrations/SqlServerPersistenceProviderModelSnapshot.cs b/src/providers/WorkflowCore.Persistence.SqlServer/Migrations/SqlServerPersistenceProviderModelSnapshot.cs index 62d840f51..e230f1026 100644 --- a/src/providers/WorkflowCore.Persistence.SqlServer/Migrations/SqlServerPersistenceProviderModelSnapshot.cs +++ b/src/providers/WorkflowCore.Persistence.SqlServer/Migrations/SqlServerPersistenceProviderModelSnapshot.cs @@ -14,7 +14,7 @@ partial class SqlServerPersistenceProviderModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder - .HasAnnotation("ProductVersion", "1.1.1") + .HasAnnotation("ProductVersion", "1.1.2") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("WorkflowCore.Persistence.EntityFramework.Models.PersistedEvent", b => @@ -220,6 +220,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("NextExecution"); + b.Property("Reference") + .HasMaxLength(200); + b.Property("Status"); b.Property("Version"); diff --git a/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj b/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj index 85bb29bce..1406b2642 100644 --- a/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj +++ b/src/providers/WorkflowCore.Persistence.SqlServer/WorkflowCore.Persistence.SqlServer.csproj @@ -29,11 +29,11 @@ - - + + All - + diff --git a/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj b/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj index 1cdc8487f..57a3c8f4a 100644 --- a/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj +++ b/src/providers/WorkflowCore.Persistence.Sqlite/WorkflowCore.Persistence.Sqlite.csproj @@ -29,7 +29,7 @@ - + diff --git a/src/providers/WorkflowCore.QueueProviders.RabbitMQ/WorkflowCore.QueueProviders.RabbitMQ.csproj b/src/providers/WorkflowCore.QueueProviders.RabbitMQ/WorkflowCore.QueueProviders.RabbitMQ.csproj index ca2cada13..5c75e91fb 100644 --- a/src/providers/WorkflowCore.QueueProviders.RabbitMQ/WorkflowCore.QueueProviders.RabbitMQ.csproj +++ b/src/providers/WorkflowCore.QueueProviders.RabbitMQ/WorkflowCore.QueueProviders.RabbitMQ.csproj @@ -28,8 +28,8 @@ - - + + diff --git a/src/samples/WorkflowCore.Sample03/Program.cs b/src/samples/WorkflowCore.Sample03/Program.cs index 715c248f1..d778ba6ba 100644 --- a/src/samples/WorkflowCore.Sample03/Program.cs +++ b/src/samples/WorkflowCore.Sample03/Program.cs @@ -28,7 +28,6 @@ public static void Main(string[] args) }; host.StartWorkflow("PassingDataWorkflow", 1, initialData); - host.StartWorkflow("PassingDataWorkflow", 1, new MyDataClass() { Value1 = 3, Value2 = 4 }); Console.ReadLine(); host.Stop(); diff --git a/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs b/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs index 3d7e03d5b..209229812 100644 --- a/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs +++ b/src/samples/WorkflowCore.Sample03/Steps/AddNumbers.cs @@ -18,10 +18,6 @@ public class AddNumbers : StepBodyAsync public override async Task RunAsync(IStepExecutionContext context) { - if (Input1 == 2) - await Task.Delay(2000); - //System.Threading.Thread.Sleep(2000); - Output = (Input1 + Input2); return ExecutionResult.Next(); } From 236c8e7852e4cb8a9e5471f65f25b3b687d303ce Mon Sep 17 00:00:00 2001 From: Daniel Gerlag Date: Sat, 22 Jul 2017 10:55:55 -0700 Subject: [PATCH 9/9] package versions --- src/WorkflowCore/WorkflowCore.csproj | 2 +- .../WorkflowCore.Sample01.csproj | 12 ++++++------ .../WorkflowCore.Sample02.csproj | 12 ++++++------ .../WorkflowCore.Sample03.csproj | 12 ++++++------ .../WorkflowCore.Sample04.csproj | 12 ++++++------ .../WorkflowCore.Sample05.csproj | 12 ++++++------ .../WorkflowCore.Sample06.csproj | 12 ++++++------ .../WorkflowCore.Sample07.csproj | 8 ++++---- .../WorkflowCore.Sample08.csproj | 12 ++++++------ .../WorkflowCore.Sample09.csproj | 6 +++--- .../WorkflowCore.Sample10.csproj | 4 ++-- src/samples/WorkflowCore.Sample13/Program.cs | 2 +- 12 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/WorkflowCore/WorkflowCore.csproj b/src/WorkflowCore/WorkflowCore.csproj index 985d45d31..0f1501972 100644 --- a/src/WorkflowCore/WorkflowCore.csproj +++ b/src/WorkflowCore/WorkflowCore.csproj @@ -25,7 +25,7 @@ - + diff --git a/src/samples/WorkflowCore.Sample01/WorkflowCore.Sample01.csproj b/src/samples/WorkflowCore.Sample01/WorkflowCore.Sample01.csproj index 81356daa4..152ccf433 100644 --- a/src/samples/WorkflowCore.Sample01/WorkflowCore.Sample01.csproj +++ b/src/samples/WorkflowCore.Sample01/WorkflowCore.Sample01.csproj @@ -18,12 +18,12 @@ - - - - - - + + + + + + diff --git a/src/samples/WorkflowCore.Sample02/WorkflowCore.Sample02.csproj b/src/samples/WorkflowCore.Sample02/WorkflowCore.Sample02.csproj index 0f5e0aa42..af734aba9 100644 --- a/src/samples/WorkflowCore.Sample02/WorkflowCore.Sample02.csproj +++ b/src/samples/WorkflowCore.Sample02/WorkflowCore.Sample02.csproj @@ -17,12 +17,12 @@ - - - - - - + + + + + + diff --git a/src/samples/WorkflowCore.Sample03/WorkflowCore.Sample03.csproj b/src/samples/WorkflowCore.Sample03/WorkflowCore.Sample03.csproj index abd5b389b..66a89e611 100644 --- a/src/samples/WorkflowCore.Sample03/WorkflowCore.Sample03.csproj +++ b/src/samples/WorkflowCore.Sample03/WorkflowCore.Sample03.csproj @@ -18,12 +18,12 @@ - - - - - - + + + + + + diff --git a/src/samples/WorkflowCore.Sample04/WorkflowCore.Sample04.csproj b/src/samples/WorkflowCore.Sample04/WorkflowCore.Sample04.csproj index ac460da1b..86bbc7789 100644 --- a/src/samples/WorkflowCore.Sample04/WorkflowCore.Sample04.csproj +++ b/src/samples/WorkflowCore.Sample04/WorkflowCore.Sample04.csproj @@ -25,12 +25,12 @@ - - - - - - + + + + + + diff --git a/src/samples/WorkflowCore.Sample05/WorkflowCore.Sample05.csproj b/src/samples/WorkflowCore.Sample05/WorkflowCore.Sample05.csproj index 7139b8954..89f759925 100644 --- a/src/samples/WorkflowCore.Sample05/WorkflowCore.Sample05.csproj +++ b/src/samples/WorkflowCore.Sample05/WorkflowCore.Sample05.csproj @@ -19,12 +19,12 @@ - - - - - - + + + + + + diff --git a/src/samples/WorkflowCore.Sample06/WorkflowCore.Sample06.csproj b/src/samples/WorkflowCore.Sample06/WorkflowCore.Sample06.csproj index 40fbdc7af..c42fbaf8e 100644 --- a/src/samples/WorkflowCore.Sample06/WorkflowCore.Sample06.csproj +++ b/src/samples/WorkflowCore.Sample06/WorkflowCore.Sample06.csproj @@ -20,12 +20,12 @@ - - - - - - + + + + + + diff --git a/src/samples/WorkflowCore.Sample07/WorkflowCore.Sample07.csproj b/src/samples/WorkflowCore.Sample07/WorkflowCore.Sample07.csproj index fd0348c28..409704743 100644 --- a/src/samples/WorkflowCore.Sample07/WorkflowCore.Sample07.csproj +++ b/src/samples/WorkflowCore.Sample07/WorkflowCore.Sample07.csproj @@ -25,10 +25,10 @@ - - - - + + + + diff --git a/src/samples/WorkflowCore.Sample08/WorkflowCore.Sample08.csproj b/src/samples/WorkflowCore.Sample08/WorkflowCore.Sample08.csproj index a83ef9ea5..61678331d 100644 --- a/src/samples/WorkflowCore.Sample08/WorkflowCore.Sample08.csproj +++ b/src/samples/WorkflowCore.Sample08/WorkflowCore.Sample08.csproj @@ -23,12 +23,12 @@ - - - - - - + + + + + + diff --git a/src/samples/WorkflowCore.Sample09/WorkflowCore.Sample09.csproj b/src/samples/WorkflowCore.Sample09/WorkflowCore.Sample09.csproj index dded3bd37..5af9110bb 100644 --- a/src/samples/WorkflowCore.Sample09/WorkflowCore.Sample09.csproj +++ b/src/samples/WorkflowCore.Sample09/WorkflowCore.Sample09.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/src/samples/WorkflowCore.Sample10/WorkflowCore.Sample10.csproj b/src/samples/WorkflowCore.Sample10/WorkflowCore.Sample10.csproj index 4dedfae1f..5af9110bb 100644 --- a/src/samples/WorkflowCore.Sample10/WorkflowCore.Sample10.csproj +++ b/src/samples/WorkflowCore.Sample10/WorkflowCore.Sample10.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/src/samples/WorkflowCore.Sample13/Program.cs b/src/samples/WorkflowCore.Sample13/Program.cs index 5a9263579..ac6016e66 100644 --- a/src/samples/WorkflowCore.Sample13/Program.cs +++ b/src/samples/WorkflowCore.Sample13/Program.cs @@ -30,7 +30,7 @@ private static IServiceProvider ConfigureServices() services.AddLogging(); services.AddWorkflow(); //services.AddWorkflow(x => x.UseMongoDB(@"mongodb://localhost:27017", "workflow-test002")); - //services.AddWorkflow(x => x.UseSqlServer(@"Server=.\SQLEXPRESS;Database=WorkflowCoreTest001;Trusted_Connection=True;", true, true)); + //services.AddWorkflow(x => x.UseSqlServer(@"Server=.\SQLEXPRESS;Database=WorkflowCoreTest007;Trusted_Connection=True;", true, true)); //services.AddWorkflow(x => x.UseSqlite(@"Data Source=wfc001.db;", true)); //services.AddWorkflow(x =>