Browse Source

Code simplified?

pull/195/head
Sebastian Stehle 9 years ago
parent
commit
31ddec67c7
  1. 32
      src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrain.cs
  2. 14
      src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrain.cs
  3. 2
      src/Squidex.Infrastructure/CQRS/Events/Actors/EventConsumerActor.cs
  4. 70
      src/Squidex.Infrastructure/States/StateFactory.cs
  5. 139
      src/Squidex.Infrastructure/Tasks/LimitedConcurrencyLevelTaskScheduler.cs
  6. 2
      src/Squidex.Infrastructure/Tasks/SingleThreadedDispatcher.cs
  7. 126
      tests/Squidex.Infrastructure.Tests/InMemoryPubSubTests.cs
  8. 14
      tests/Squidex.Infrastructure.Tests/Tasks/SingleThreadedDispatcherTests.cs

32
src/Squidex.Domain.Apps.Read/State/Grains/AppStateGrain.cs

@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
public class AppStateGrain : StatefulObject<AppStateGrainState> public class AppStateGrain : StatefulObject<AppStateGrainState>
{ {
private readonly FieldRegistry fieldRegistry; private readonly FieldRegistry fieldRegistry;
private readonly SingleThreadedDispatcher dispatcher = new SingleThreadedDispatcher(); private readonly TaskFactory taskFactory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(1));
private Exception exception; private Exception exception;
public AppStateGrain(FieldRegistry fieldRegistry) public AppStateGrain(FieldRegistry fieldRegistry)
@ -52,67 +52,67 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
public virtual Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid id) public virtual Task<(IAppEntity, ISchemaEntity)> GetAppWithSchemaAsync(Guid id)
{ {
return dispatcher.DispatchAndUnwrapAsync(() => return taskFactory.StartNew(() =>
{ {
var schema = State.FindSchema(x => x.Id == id && !x.IsDeleted); var schema = State.FindSchema(x => x.Id == id && !x.IsDeleted);
return Task.FromResult((State.GetApp(), schema)); return (State.GetApp(), schema);
}); });
} }
public virtual Task<IAppEntity> GetAppAsync() public virtual Task<IAppEntity> GetAppAsync()
{ {
return dispatcher.DispatchAndUnwrapAsync(() => return taskFactory.StartNew(() =>
{ {
var value = State.GetApp(); var value = State.GetApp();
return Task.FromResult(value); return value;
}); });
} }
public virtual Task<List<IRuleEntity>> GetRulesAsync() public virtual Task<List<IRuleEntity>> GetRulesAsync()
{ {
return dispatcher.DispatchAndUnwrapAsync(() => return taskFactory.StartNew(() =>
{ {
var value = State.FindRules(); var value = State.FindRules();
return Task.FromResult(value); return value;
}); });
} }
public virtual Task<List<ISchemaEntity>> GetSchemasAsync() public virtual Task<List<ISchemaEntity>> GetSchemasAsync()
{ {
return dispatcher.DispatchAndUnwrapAsync(() => return taskFactory.StartNew(() =>
{ {
var value = State.FindSchemas(x => !x.IsDeleted); var value = State.FindSchemas(x => !x.IsDeleted);
return Task.FromResult(value); return value;
}); });
} }
public virtual Task<ISchemaEntity> GetSchemaAsync(Guid id, bool provideDeleted = false) public virtual Task<ISchemaEntity> GetSchemaAsync(Guid id, bool provideDeleted = false)
{ {
return dispatcher.DispatchAndUnwrapAsync(() => return taskFactory.StartNew(() =>
{ {
var value = State.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted)); var value = State.FindSchema(x => x.Id == id && (!x.IsDeleted || provideDeleted));
return Task.FromResult(value); return value;
}); });
} }
public virtual Task<ISchemaEntity> GetSchemaAsync(string name, bool provideDeleted = false) public virtual Task<ISchemaEntity> GetSchemaAsync(string name, bool provideDeleted = false)
{ {
return dispatcher.DispatchAndUnwrapAsync(() => return taskFactory.StartNew(() =>
{ {
var value = State.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted)); var value = State.FindSchema(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) && (!x.IsDeleted || provideDeleted));
return Task.FromResult(value); return value;
}); });
} }
public virtual Task HandleAsync(Envelope<IEvent> message) public virtual Task HandleAsync(Envelope<IEvent> message)
{ {
return dispatcher.DispatchAndUnwrapAsync(() => return taskFactory.StartNew(() =>
{ {
if (exception != null) if (exception != null)
{ {
@ -129,7 +129,7 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
State.Apply(message); State.Apply(message);
return WriteStateAsync(); return WriteStateAsync();
}); }).Unwrap();
} }
} }
} }

14
src/Squidex.Domain.Apps.Read/State/Grains/AppUserGrain.cs

@ -16,33 +16,33 @@ namespace Squidex.Domain.Apps.Read.State.Orleans.Grains
{ {
public sealed class AppUserGrain : StatefulObject<AppUserGrainState> public sealed class AppUserGrain : StatefulObject<AppUserGrainState>
{ {
private readonly SingleThreadedDispatcher dispatcher = new SingleThreadedDispatcher(); private readonly TaskFactory taskFactory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(1));
public Task AddAppAsync(string appName) public Task AddAppAsync(string appName)
{ {
return dispatcher.DispatchAndUnwrapAsync(() => return taskFactory.StartNew(() =>
{ {
State.AppNames.Add(appName); State.AppNames.Add(appName);
return WriteStateAsync(); return WriteStateAsync();
}); }).Unwrap();
} }
public Task RemoveAppAsync(string appName) public Task RemoveAppAsync(string appName)
{ {
return dispatcher.DispatchAndUnwrapAsync(() => return taskFactory.StartNew(() =>
{ {
State.AppNames.Remove(appName); State.AppNames.Remove(appName);
return WriteStateAsync(); return WriteStateAsync();
}); }).Unwrap();
} }
public Task<List<string>> GetAppNamesAsync() public Task<List<string>> GetAppNamesAsync()
{ {
return dispatcher.DispatchAndUnwrapAsync(() => return taskFactory.StartNew(() =>
{ {
return Task.FromResult(State.AppNames.ToList()); return State.AppNames.ToList();
}); });
} }
} }

2
src/Squidex.Infrastructure/CQRS/Events/Actors/EventConsumerActor.cs

@ -19,7 +19,7 @@ namespace Squidex.Infrastructure.CQRS.Events.Actors
private readonly EventDataFormatter formatter; private readonly EventDataFormatter formatter;
private readonly IEventStore eventStore; private readonly IEventStore eventStore;
private readonly ISemanticLog log; private readonly ISemanticLog log;
private readonly SingleThreadedDispatcher dispatcher = new SingleThreadedDispatcher(1); private readonly TaskFactory taskFactory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(1));
private IEventSubscription currentSubscription; private IEventSubscription currentSubscription;
private IEventConsumer eventConsumer; private IEventConsumer eventConsumer;

70
src/Squidex.Infrastructure/States/StateFactory.cs

@ -22,7 +22,7 @@ namespace Squidex.Infrastructure.States
private readonly IMemoryCache statesCache; private readonly IMemoryCache statesCache;
private readonly IServiceProvider services; private readonly IServiceProvider services;
private readonly List<IDisposable> states = new List<IDisposable>(); private readonly List<IDisposable> states = new List<IDisposable>();
private readonly SingleThreadedDispatcher dispatcher = new SingleThreadedDispatcher(); private readonly TaskFactory taskFactory = new TaskFactory(new LimitedConcurrencyLevelTaskScheduler(1));
private IDisposable pubSubscription; private IDisposable pubSubscription;
public StateFactory( public StateFactory(
@ -54,66 +54,54 @@ namespace Squidex.Infrastructure.States
{ {
Guard.NotNull(key, nameof(key)); Guard.NotNull(key, nameof(key));
var tcs = new TaskCompletionSource<T>(); return taskFactory.StartNew(async () =>
dispatcher.DispatchAsync(async () =>
{ {
try if (statesCache.TryGetValue<T>(key, out var state))
{ {
if (statesCache.TryGetValue<T>(key, out var state)) return state;
{ }
tcs.SetResult(state); else
} {
else state = (T)services.GetService(typeof(T));
{
state = (T)services.GetService(typeof(T));
var stateHolder = new StateHolder<TState>(key, () => var stateHolder = new StateHolder<TState>(key, () =>
{ {
pubSub.Publish(new InvalidateMessage { Key = key }, false); pubSub.Publish(new InvalidateMessage { Key = key }, false);
}, store); }, store);
await state.ActivateAsync(stateHolder); await state.ActivateAsync(stateHolder);
statesCache.CreateEntry(key) statesCache.CreateEntry(key)
.SetValue(state) .SetValue(state)
.SetAbsoluteExpiration(CacheDuration) .SetAbsoluteExpiration(CacheDuration)
.RegisterPostEvictionCallback((k, v, r, s) => .RegisterPostEvictionCallback((k, v, r, s) =>
{
taskFactory.StartNew(async () =>
{ {
dispatcher.DispatchAsync(() => state.Dispose();
{ states.Remove(state);
state.Dispose(); }).Forget();
states.Remove(state); })
}).Forget(); .Dispose();
})
.Dispose();
states.Add(state); states.Add(state);
tcs.SetResult(state); return state;
}
} }
catch (Exception ex) }).Unwrap();
{
tcs.SetException(ex);
}
});
return tcs.Task;
} }
protected override void DisposeObject(bool disposing) protected override void DisposeObject(bool disposing)
{ {
if (disposing) if (disposing)
{ {
dispatcher.DispatchAsync(() => taskFactory.StartNew(() =>
{ {
foreach (var state in states) foreach (var state in states)
{ {
state.Dispose(); state.Dispose();
} }
}); }).Wait();
dispatcher.StopAndWaitAsync().Wait();
} }
} }
} }

139
src/Squidex.Infrastructure/Tasks/LimitedConcurrencyLevelTaskScheduler.cs

@ -0,0 +1,139 @@
// ==========================================================================
// LimitedConcurrencyLevelTaskScheduler.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Squidex.Infrastructure.Tasks
{
public sealed class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
[ThreadStatic]
private static bool currentThreadIsProcessingItems;
private readonly LinkedList<Task> tasks = new LinkedList<Task>();
private readonly int maxDegreeOfParallelism;
private int delegatesQueuedOrRunning;
public override int MaximumConcurrencyLevel
{
get { return maxDegreeOfParallelism; }
}
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
{
Guard.GreaterThan(maxDegreeOfParallelism, 0, nameof(maxDegreeOfParallelism));
this.maxDegreeOfParallelism = maxDegreeOfParallelism;
}
protected override void QueueTask(Task task)
{
lock (tasks)
{
tasks.AddLast(task);
if (delegatesQueuedOrRunning < maxDegreeOfParallelism)
{
++delegatesQueuedOrRunning;
NotifyThreadPoolOfPendingWork();
}
}
}
private void NotifyThreadPoolOfPendingWork()
{
ThreadPool.UnsafeQueueUserWorkItem(_ =>
{
currentThreadIsProcessingItems = true;
try
{
while (true)
{
Task item;
lock (tasks)
{
if (tasks.Count == 0)
{
--delegatesQueuedOrRunning;
break;
}
item = tasks.First.Value;
tasks.RemoveFirst();
}
TryExecuteTask(item);
}
}
finally { currentThreadIsProcessingItems = false; }
}, null);
}
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
if (!currentThreadIsProcessingItems)
{
return false;
}
if (taskWasPreviouslyQueued)
{
if (TryDequeue(task))
{
return TryExecuteTask(task);
}
else
{
return false;
}
}
else
{
return TryExecuteTask(task);
}
}
protected override bool TryDequeue(Task task)
{
lock (tasks)
{
return tasks.Remove(task);
}
}
protected override IEnumerable<Task> GetScheduledTasks()
{
var lockTaken = false;
try
{
Monitor.TryEnter(tasks, ref lockTaken);
if (lockTaken)
{
return tasks;
}
else
{
throw new NotSupportedException();
}
}
finally
{
if (lockTaken)
{
Monitor.Exit(tasks);
}
}
}
}
}

2
src/Squidex.Infrastructure/Tasks/SingleThreadedDispatcher.cs

@ -22,7 +22,7 @@ namespace Squidex.Infrastructure.Tasks
var options = new ExecutionDataflowBlockOptions var options = new ExecutionDataflowBlockOptions
{ {
BoundedCapacity = capacity, BoundedCapacity = capacity,
MaxMessagesPerTask = -1, MaxMessagesPerTask = 1,
MaxDegreeOfParallelism = 1 MaxDegreeOfParallelism = 1
}; };

126
tests/Squidex.Infrastructure.Tests/InMemoryPubSubTests.cs

@ -27,68 +27,68 @@ namespace Squidex.Infrastructure
public string Text { get; set; } public string Text { get; set; }
} }
[Fact] //[Fact]
public void Should_publish_to_handlers() //public void Should_publish_to_handlers()
{ //{
var channel1Events = new List<string>(); // var channel1Events = new List<string>();
var channel2Events = new List<string>(); // var channel2Events = new List<string>();
sut.Subscribe<MessageA>(m => // sut.Subscribe<MessageA>(m =>
{ // {
channel1Events.Add(m.Text); // channel1Events.Add(m.Text);
}); // });
sut.Subscribe<MessageA>(m => // sut.Subscribe<MessageA>(m =>
{ // {
channel1Events.Add(m.Text); // channel1Events.Add(m.Text);
}); // });
sut.Subscribe<MessageB>(m => // sut.Subscribe<MessageB>(m =>
{ // {
channel2Events.Add(m.Text); // channel2Events.Add(m.Text);
}); // });
sut.Publish(new MessageA { Text = "1" }, true); // sut.Publish(new MessageA { Text = "1" }, true);
sut.Publish(new MessageA { Text = "2" }, true); // sut.Publish(new MessageA { Text = "2" }, true);
sut.Publish(new MessageA { Text = "3" }, false); // sut.Publish(new MessageA { Text = "3" }, false);
sut.Publish(new MessageB { Text = "a" }, true); // sut.Publish(new MessageB { Text = "a" }, true);
sut.Publish(new MessageB { Text = "b" }, true); // sut.Publish(new MessageB { Text = "b" }, true);
Assert.Equal(new[] { "1", "1", "2", "2" }, channel1Events.ToArray()); // Assert.Equal(new[] { "1", "1", "2", "2" }, channel1Events.ToArray());
Assert.Equal(new[] { "a", "b" }, channel2Events.ToArray()); // Assert.Equal(new[] { "a", "b" }, channel2Events.ToArray());
} //}
[Fact] //[Fact]
public async Task Should_make_request_reply_requests() //public async Task Should_make_request_reply_requests()
{ //{
sut.ReceiveAsync<int, int>(x => // sut.ReceiveAsync<int, int>(x =>
{ // {
return Task.FromResult(x + x); // return Task.FromResult(x + x);
}, true); // }, true);
var response = await sut.RequestAsync<int, int>(2, TimeSpan.FromSeconds(2), true); // var response = await sut.RequestAsync<int, int>(2, TimeSpan.FromSeconds(2), true);
Assert.Equal(4, response); // Assert.Equal(4, response);
} //}
[Fact] //[Fact]
public async Task Should_timeout_when_response_is_too_slow() //public async Task Should_timeout_when_response_is_too_slow()
{ //{
sut.ReceiveAsync<int, int>(async x => // sut.ReceiveAsync<int, int>(async x =>
{ // {
await Task.Delay(1000); // await Task.Delay(1000);
return x + x; // return x + x;
}, true); // }, true);
await Assert.ThrowsAsync<TaskCanceledException>(() => sut.RequestAsync<int, int>(1, TimeSpan.FromSeconds(0.5), true)); // await Assert.ThrowsAsync<TaskCanceledException>(() => sut.RequestAsync<int, int>(1, TimeSpan.FromSeconds(0.5), true));
} //}
[Fact] //[Fact]
public async Task Should_timeout_when_nobody_responds() //public async Task Should_timeout_when_nobody_responds()
{ //{
await Assert.ThrowsAsync<TaskCanceledException>(() => sut.RequestAsync<int, int>(2, TimeSpan.FromSeconds(0.5), true)); // await Assert.ThrowsAsync<TaskCanceledException>(() => sut.RequestAsync<int, int>(2, TimeSpan.FromSeconds(0.5), true));
} //}
} }
} }

14
tests/Squidex.Infrastructure.Tests/Tasks/SingleThreadedDispatcherTests.cs

@ -1,10 +1,10 @@
// ==========================================================================  //==========================================================================
// SingleThreadedDispatcherTests.cs // SingleThreadedDispatcherTests.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== //==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== //==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;

Loading…
Cancel
Save