mirror of https://github.com/Squidex/squidex.git
458 changed files with 1112 additions and 3231 deletions
@ -0,0 +1,28 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using NodaTime; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules |
||||
|
{ |
||||
|
public sealed class RuleJobUpdate |
||||
|
{ |
||||
|
public string? ExecutionDump { get; set; } |
||||
|
|
||||
|
public RuleResult ExecutionResult { get; set; } |
||||
|
|
||||
|
public RuleJobResult JobResult { get; set; } |
||||
|
|
||||
|
public TimeSpan Elapsed { get; set; } |
||||
|
|
||||
|
public Instant Finished { get; set; } |
||||
|
|
||||
|
public Instant? JobNext { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
using Squidex.Infrastructure; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Runner |
||||
|
{ |
||||
|
public sealed class GrainRuleRunnerService : IRuleRunnerService |
||||
|
{ |
||||
|
private readonly IGrainFactory grainFactory; |
||||
|
|
||||
|
public GrainRuleRunnerService(IGrainFactory grainFactory) |
||||
|
{ |
||||
|
Guard.NotNull(grainFactory, nameof(grainFactory)); |
||||
|
|
||||
|
this.grainFactory = grainFactory; |
||||
|
} |
||||
|
|
||||
|
public Task CancelAsync(Guid appId) |
||||
|
{ |
||||
|
var grain = grainFactory.GetGrain<IRuleRunnerGrain>(appId); |
||||
|
|
||||
|
return grain.CancelAsync(); |
||||
|
} |
||||
|
|
||||
|
public Task<Guid?> GetRunningRuleIdAsync(Guid appId) |
||||
|
{ |
||||
|
var grain = grainFactory.GetGrain<IRuleRunnerGrain>(appId); |
||||
|
|
||||
|
return grain.GetRunningRuleIdAsync(); |
||||
|
} |
||||
|
|
||||
|
public Task RunAsync(Guid appId, Guid ruleId) |
||||
|
{ |
||||
|
var grain = grainFactory.GetGrain<IRuleRunnerGrain>(appId); |
||||
|
|
||||
|
return grain.RunAsync(ruleId); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Runner |
||||
|
{ |
||||
|
public interface IRuleRunnerGrain : IGrainWithGuidKey |
||||
|
{ |
||||
|
Task RunAsync(Guid ruleId); |
||||
|
|
||||
|
Task CancelAsync(); |
||||
|
|
||||
|
Task<Guid?> GetRunningRuleIdAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Runner |
||||
|
{ |
||||
|
public interface IRuleRunnerService |
||||
|
{ |
||||
|
Task RunAsync(Guid appId, Guid ruleId); |
||||
|
|
||||
|
Task CancelAsync(Guid appId); |
||||
|
|
||||
|
Task<Guid?> GetRunningRuleIdAsync(Guid appId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,202 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
using Orleans.Runtime; |
||||
|
using Squidex.Domain.Apps.Core.HandleRules; |
||||
|
using Squidex.Domain.Apps.Entities.Rules.Repositories; |
||||
|
using Squidex.Domain.Apps.Events; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.EventSourcing; |
||||
|
using Squidex.Infrastructure.Log; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
using Squidex.Infrastructure.States; |
||||
|
using Squidex.Infrastructure.Tasks; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Rules.Runner |
||||
|
{ |
||||
|
public sealed class RuleRunnerGrain : GrainOfGuid, IRuleRunnerGrain, IRemindable |
||||
|
{ |
||||
|
private readonly IGrainState<State> state; |
||||
|
private readonly IAppProvider appProvider; |
||||
|
private readonly IEventStore eventStore; |
||||
|
private readonly IEventDataFormatter eventDataFormatter; |
||||
|
private readonly IRuleEventRepository ruleEventRepository; |
||||
|
private readonly RuleService ruleService; |
||||
|
private readonly ISemanticLog log; |
||||
|
private CancellationTokenSource? currentTaskToken; |
||||
|
private IGrainReminder? currentReminder; |
||||
|
private bool isStopping; |
||||
|
|
||||
|
[CollectionName("Rules_Runner")] |
||||
|
public sealed class State |
||||
|
{ |
||||
|
public Guid? RuleId { get; set; } |
||||
|
|
||||
|
public string? Position { get; set; } |
||||
|
} |
||||
|
|
||||
|
public RuleRunnerGrain( |
||||
|
IGrainState<State> state, |
||||
|
IAppProvider appProvider, |
||||
|
IEventStore eventStore, |
||||
|
IEventDataFormatter eventDataFormatter, |
||||
|
IRuleEventRepository ruleEventRepository, |
||||
|
RuleService ruleService, |
||||
|
ISemanticLog log) |
||||
|
{ |
||||
|
Guard.NotNull(state); |
||||
|
Guard.NotNull(appProvider); |
||||
|
Guard.NotNull(eventStore); |
||||
|
Guard.NotNull(eventDataFormatter); |
||||
|
Guard.NotNull(ruleEventRepository); |
||||
|
Guard.NotNull(ruleService); |
||||
|
Guard.NotNull(log); |
||||
|
|
||||
|
this.state = state; |
||||
|
this.appProvider = appProvider; |
||||
|
this.eventStore = eventStore; |
||||
|
this.eventDataFormatter = eventDataFormatter; |
||||
|
this.ruleEventRepository = ruleEventRepository; |
||||
|
this.ruleService = ruleService; |
||||
|
this.log = log; |
||||
|
} |
||||
|
|
||||
|
protected override Task OnActivateAsync(Guid key) |
||||
|
{ |
||||
|
EnsureIsRunning(); |
||||
|
|
||||
|
return base.OnActivateAsync(key); |
||||
|
} |
||||
|
|
||||
|
public override Task OnDeactivateAsync() |
||||
|
{ |
||||
|
isStopping = true; |
||||
|
|
||||
|
currentTaskToken?.Cancel(); |
||||
|
|
||||
|
return base.OnDeactivateAsync(); |
||||
|
} |
||||
|
|
||||
|
public Task CancelAsync() |
||||
|
{ |
||||
|
currentTaskToken?.Cancel(); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public Task<Guid?> GetRunningRuleIdAsync() |
||||
|
{ |
||||
|
return Task.FromResult(state.Value.RuleId); |
||||
|
} |
||||
|
|
||||
|
public async Task RunAsync(Guid ruleId) |
||||
|
{ |
||||
|
if (currentTaskToken != null) |
||||
|
{ |
||||
|
throw new DomainException("Another rule is already running."); |
||||
|
} |
||||
|
|
||||
|
state.Value = new State |
||||
|
{ |
||||
|
RuleId = ruleId |
||||
|
}; |
||||
|
|
||||
|
EnsureIsRunning(); |
||||
|
|
||||
|
await state.WriteAsync(); |
||||
|
} |
||||
|
|
||||
|
private void EnsureIsRunning() |
||||
|
{ |
||||
|
if (state.Value.RuleId.HasValue && currentTaskToken == null) |
||||
|
{ |
||||
|
currentTaskToken = new CancellationTokenSource(); |
||||
|
|
||||
|
Process(state.Value, currentTaskToken.Token); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void Process(State job, CancellationToken ct) |
||||
|
{ |
||||
|
ProcessAsync(job, ct).Forget(); |
||||
|
} |
||||
|
|
||||
|
private async Task ProcessAsync(State job, CancellationToken ct) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
currentReminder = await RegisterOrUpdateReminder("KeepAlive", TimeSpan.Zero, TimeSpan.FromMinutes(2)); |
||||
|
|
||||
|
var rules = await appProvider.GetRulesAsync(Key); |
||||
|
|
||||
|
var rule = rules.Find(x => x.Id == job.RuleId); |
||||
|
|
||||
|
if (rule == null) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Cannot find rule."); |
||||
|
} |
||||
|
|
||||
|
await eventStore.QueryAsync(async storedEvent => |
||||
|
{ |
||||
|
var @event = eventDataFormatter.Parse(storedEvent.Data); |
||||
|
|
||||
|
var jobs = await ruleService.CreateJobsAsync(rule.RuleDef, rule.Id, @event); |
||||
|
|
||||
|
foreach (var job in jobs) |
||||
|
{ |
||||
|
await ruleEventRepository.EnqueueAsync(job, job.Created, ct); |
||||
|
} |
||||
|
|
||||
|
job.Position = storedEvent.EventPosition; |
||||
|
|
||||
|
await state.WriteAsync(); |
||||
|
}, SquidexHeaders.AppId, Key.ToString(), job.Position, ct); |
||||
|
} |
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
catch (Exception ex) |
||||
|
{ |
||||
|
log.LogError(ex, w => w |
||||
|
.WriteProperty("action", "runeRule") |
||||
|
.WriteProperty("status", "failed") |
||||
|
.WriteProperty("ruleId", job.RuleId?.ToString())); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
if (!isStopping) |
||||
|
{ |
||||
|
job.RuleId = null; |
||||
|
job.Position = null; |
||||
|
|
||||
|
await state.WriteAsync(); |
||||
|
|
||||
|
if (currentReminder != null) |
||||
|
{ |
||||
|
await UnregisterReminder(currentReminder); |
||||
|
|
||||
|
currentReminder = null; |
||||
|
} |
||||
|
|
||||
|
currentTaskToken = null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task ReceiveReminder(string reminderName, TickStatus status) |
||||
|
{ |
||||
|
EnsureIsRunning(); |
||||
|
|
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,35 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Runtime.Serialization; |
|
||||
|
|
||||
namespace Squidex.Infrastructure |
|
||||
{ |
|
||||
[Serializable] |
|
||||
public class UniqueConstraintException : Exception |
|
||||
{ |
|
||||
public UniqueConstraintException() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public UniqueConstraintException(string message) |
|
||||
: base(message) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public UniqueConstraintException(string message, Exception inner) |
|
||||
: base(message, inner) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected UniqueConstraintException(SerializationInfo info, StreamingContext context) |
|
||||
: base(info, context) |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue