├── .gitattributes ├── .gitignore ├── AsyncOrigoSpike.Core ├── AkkaNet │ ├── AkkaEngine.cs │ ├── AkkaJournaler.cs │ ├── CommandContext.cs │ ├── Dispatcher.cs │ ├── Executor.cs │ └── JournalAcknowledgement.cs ├── AsyncOrigoSpike.Core.csproj ├── Common │ ├── CommandBatch.cs │ ├── EventStoreWriter.cs │ ├── IEngine.cs │ ├── IJournalWriter.cs │ ├── Kernel.cs │ ├── NullJournalWriter.cs │ └── TransactionPrimitives.cs ├── DisruptorNet │ ├── CommandDispatcher.cs │ ├── DisruptorEngine.cs │ ├── Journaler.cs │ ├── Request.cs │ └── SerializedTransactionHandler.cs ├── Properties │ └── AssemblyInfo.cs ├── TplDataflow │ ├── CommandRequest.cs │ ├── ExecutionPipeline.cs │ ├── QueryRequest.cs │ ├── TplBatchingJournaler.cs │ └── TplDataflowEngine.cs ├── TplNet │ ├── TplNetEngine.cs │ ├── TplNetExecutor.cs │ └── TplNetJournaler.cs └── packages.config ├── AsyncOrigoSpike.Test ├── AsyncOrigoSpike.Test.csproj ├── Properties │ └── AssemblyInfo.cs ├── TestModel │ ├── AddItemCommand.cs │ ├── GetSumQuery.cs │ ├── IntModel.cs │ └── SumCommand.cs ├── TheTests.cs └── packages.config ├── AsyncOrigoSpike.sln ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | 98 | # NuGet Packages Directory 99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 100 | packages/ 101 | 102 | # Windows Azure Build Output 103 | csx 104 | *.build.csdef 105 | 106 | # Windows Store app package directory 107 | AppPackages/ 108 | 109 | # Others 110 | sql/ 111 | *.Cache 112 | ClientBin/ 113 | [Ss]tyle[Cc]op.* 114 | ~$* 115 | *~ 116 | *.dbmdl 117 | *.[Pp]ublish.xml 118 | *.pfx 119 | *.publishsettings 120 | 121 | # RIA/Silverlight projects 122 | Generated_Code/ 123 | 124 | # Backup & report files from converting an old project file to a newer 125 | # Visual Studio version. Backup files are not needed, because we have git ;-) 126 | _UpgradeReport_Files/ 127 | Backup*/ 128 | UpgradeLog*.XML 129 | UpgradeLog*.htm 130 | 131 | # SQL Server files 132 | App_Data/*.mdf 133 | App_Data/*.ldf 134 | 135 | 136 | #LightSwitch generated files 137 | GeneratedArtifacts/ 138 | _Pvt_Extensions/ 139 | ModelManifest.xml 140 | 141 | # ========================= 142 | # Windows detritus 143 | # ========================= 144 | 145 | # Windows image file caches 146 | Thumbs.db 147 | ehthumbs.db 148 | 149 | # Folder config file 150 | Desktop.ini 151 | 152 | # Recycle Bin used on file shares 153 | $RECYCLE.BIN/ 154 | 155 | # Mac desktop service store files 156 | .DS_Store 157 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/AkkaNet/AkkaEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Akka.Actor; 4 | 5 | namespace AsyncOrigoSpike 6 | { 7 | /// 8 | /// Prevalence engine 9 | /// 10 | public class AkkaEngine : IEngine 11 | { 12 | readonly ActorSystem _actorSystem; 13 | readonly ActorRef _dispatcher; 14 | 15 | public AkkaEngine(M model, int batchSize, IJournalWriter journalWriter) 16 | { 17 | // the kernel is an origodb component which 18 | // synchronizes reads and writes to the model 19 | // will be shared by 20 | var kernel = new Kernel(model); 21 | 22 | 23 | //var journalWriter = new NullJournalWriter(); 24 | //build the chain of actors backwards 25 | _actorSystem = ActorSystem.Create("prevayler"); 26 | 27 | //executor executes commands 28 | //it could also handle queries but would allow either a single query or command at time. 29 | //better to add a group of actors that can execute queries concurrently 30 | var executor = _actorSystem.ActorOf(Props.Create(() => new Executor(kernel))); 31 | 32 | //journaler writes commands to the journal in batches or at specific intervals 33 | //before passing to the executor 34 | var journaler = _actorSystem.ActorOf(Props.Create(() => new AkkaJournaler(executor, batchSize, journalWriter))); 35 | 36 | //dispatcher prepares initial message and passes to journaler 37 | _dispatcher = _actorSystem.ActorOf(Props.Create(() => new Dispatcher(journaler, executor))); 38 | } 39 | 40 | public Task ExecuteAsync(Command command) 41 | { 42 | return _dispatcher.Ask(command); 43 | } 44 | 45 | public Task ExecuteAsync(Command command) 46 | { 47 | return _dispatcher.Ask(command); 48 | } 49 | 50 | public R Execute(Command command) 51 | { 52 | return ExecuteAsync(command).Result; 53 | } 54 | 55 | public void Execute(Command command) 56 | { 57 | ExecuteAsync(command).Wait(); 58 | } 59 | 60 | public Task ExecuteAsync(Query query) 61 | { 62 | return _dispatcher.Ask(query); 63 | } 64 | 65 | public R Execute(Query query) 66 | { 67 | return ExecuteAsync(query).Result; 68 | } 69 | 70 | public void Dispose() 71 | { 72 | _actorSystem.Shutdown(); 73 | _actorSystem.WaitForShutdown(); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/AkkaNet/AkkaJournaler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Akka.Actor; 5 | 6 | namespace AsyncOrigoSpike 7 | { 8 | /// 9 | /// Append multiple commands accumulated during a specific time period or up 10 | /// to a specific limit. 11 | /// 12 | public class AkkaJournaler : ReceiveActor 13 | { 14 | 15 | readonly private IJournalWriter _journalWriter; 16 | 17 | //number of commands at a time to journal 18 | public int BatchSize = 100; 19 | 20 | //or after a specific time elapsed, whichever comes first 21 | public TimeSpan Interval = TimeSpan.FromMilliseconds(10); 22 | 23 | //buffered commands waiting to be written to the journal 24 | private readonly List _commandBuffer = new List(200000); 25 | 26 | private readonly Queue _waitingForJournalAck = new Queue(200000); 27 | 28 | //pass on the journaled commands to this actor 29 | readonly ActorRef _executor; 30 | 31 | 32 | public AkkaJournaler(ActorRef executor, int batchSize, IJournalWriter journalWriter) 33 | { 34 | _journalWriter = journalWriter; 35 | BatchSize = batchSize; 36 | _executor = executor; 37 | Receive(t => Accept(t)); 38 | Receive(_ => Go()); 39 | Receive(_ => _executor.Tell(_waitingForJournalAck.Dequeue())); 40 | 41 | SetReceiveTimeout(Interval); 42 | } 43 | 44 | private void Go() 45 | { 46 | if (_commandBuffer.Count > 0) 47 | { 48 | var self = Self; 49 | var batch = _commandBuffer.ToArray(); 50 | var task = _journalWriter.AppendAsync(batch.Select(ctx => (Command)ctx.Transaction)); 51 | _commandBuffer.Clear(); 52 | _waitingForJournalAck.Enqueue(batch); 53 | task.ContinueWith(t => self.Tell(JournalAcknowledgement.Instance)); 54 | } 55 | } 56 | 57 | private void Accept(RequestContext command) 58 | { 59 | _commandBuffer.Add(command); 60 | if (_commandBuffer.Count == BatchSize) Go(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/AkkaNet/CommandContext.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | 3 | namespace AsyncOrigoSpike 4 | { 5 | public sealed class RequestContext 6 | { 7 | public readonly object Transaction; 8 | public readonly ActorRef Initiator; 9 | 10 | public RequestContext(object transaction, ActorRef actorRef) 11 | { 12 | Transaction = transaction; 13 | Initiator = actorRef; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/AkkaNet/Dispatcher.cs: -------------------------------------------------------------------------------- 1 | using Akka.Actor; 2 | 3 | namespace AsyncOrigoSpike 4 | { 5 | public class Dispatcher : ReceiveActor 6 | { 7 | readonly ActorRef _journalWriter; 8 | readonly ActorRef _executor; 9 | 10 | public Dispatcher(ActorRef journalWriter, ActorRef executor) 11 | { 12 | _executor = executor; 13 | _journalWriter = journalWriter; 14 | Receive(command => _journalWriter.Tell(new RequestContext(command, Sender))); 15 | Receive(query => _executor.Tell(new RequestContext(query, Sender))); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/AkkaNet/Executor.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Akka.Actor; 3 | 4 | namespace AsyncOrigoSpike 5 | { 6 | public class Executor : ReceiveActor 7 | { 8 | readonly Kernel _kernel; 9 | public Executor(Kernel kernel) 10 | { 11 | _kernel = kernel; 12 | Receive(commands => ExecuteCommands(commands)); 13 | Receive(ctx => ExecuteQuery(ctx)); 14 | } 15 | 16 | private void ExecuteQuery(RequestContext queryContext) 17 | { 18 | var result = _kernel.Execute((Query) queryContext.Transaction); 19 | queryContext.Initiator.Tell(result, Context.Parent); 20 | } 21 | 22 | private void ExecuteCommands(IEnumerable commandContexts) 23 | { 24 | foreach (var context in commandContexts) 25 | { 26 | var result = _kernel.Execute((Command)context.Transaction); 27 | 28 | //send a return message to the external caller 29 | // will correlate with the call to Ask<>() in AkkaEngine.ExecuteAsync() 30 | context.Initiator.Tell(result, Context.Parent); 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/AkkaNet/JournalAcknowledgement.cs: -------------------------------------------------------------------------------- 1 | namespace AsyncOrigoSpike 2 | { 3 | sealed class JournalAcknowledgement 4 | { 5 | public static JournalAcknowledgement Instance = new JournalAcknowledgement(); 6 | private JournalAcknowledgement() 7 | { 8 | 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/AsyncOrigoSpike.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E0C71B98-17AF-4E95-ABC3-E8D31C631696} 8 | Library 9 | Properties 10 | AsyncOrigoSpike 11 | AsyncOrigoSpike.Core 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\Akka.0.6.3\lib\net45\Akka.dll 35 | 36 | 37 | ..\packages\Disruptor.Net.2.10.0\lib\Disruptor.dll 38 | 39 | 40 | ..\packages\EventStore.Client.3.0.0\lib\net40\EventStore.ClientAPI.dll 41 | 42 | 43 | ..\packages\Metrics.NET.0.2.8\lib\net45\Metrics.dll 44 | 45 | 46 | False 47 | ..\packages\Newtonsoft.Json.6.0.1\lib\net45\Newtonsoft.Json.dll 48 | 49 | 50 | ..\packages\NUnit.2.6.3\lib\nunit.framework.dll 51 | 52 | 53 | 54 | 55 | False 56 | ..\packages\Microsoft.Tpl.Dataflow.4.5.23\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 105 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/Common/CommandBatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace AsyncOrigoSpike 6 | { 7 | 8 | [Serializable] 9 | public class CommandBatch 10 | { 11 | public readonly Command[] Commands; 12 | 13 | public CommandBatch(IEnumerable commands) 14 | { 15 | Commands = commands.ToArray(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/Common/EventStoreWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Runtime.Serialization; 6 | using System.Runtime.Serialization.Formatters.Binary; 7 | using System.Threading.Tasks; 8 | using EventStore.ClientAPI; 9 | 10 | namespace AsyncOrigoSpike 11 | { 12 | /// 13 | /// IJournalWriter implementation that writes to an EventStore 3 instance 14 | /// 15 | public class EventStoreWriter : IJournalWriter 16 | { 17 | private readonly IEventStoreConnection _eventStore; 18 | private readonly IFormatter _formatter; 19 | 20 | public EventStoreWriter(IEventStoreConnection connection, IFormatter formatter = null) 21 | { 22 | _formatter = formatter ?? new BinaryFormatter(); 23 | _eventStore = connection; 24 | } 25 | 26 | public Task AppendAsync(IEnumerable commands) 27 | { 28 | return _eventStore.AppendToStreamAsync("origo", ExpectedVersion.Any, ToEventData(commands)); 29 | } 30 | 31 | private EventData ToEventData(IEnumerable commands) 32 | { 33 | var id = Guid.NewGuid(); 34 | var stream = new MemoryStream(); 35 | _formatter.Serialize(stream, new CommandBatch(commands)); 36 | return new EventData(id, "origo-batch", false, stream.ToArray(), null); 37 | } 38 | 39 | public void Dispose() 40 | { 41 | _eventStore.Close(); 42 | } 43 | 44 | /// 45 | /// create an instance with an open connection to an event store instance 46 | /// 47 | /// 48 | public static IJournalWriter Create() 49 | { 50 | var endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1113); 51 | var connection = EventStoreConnection.Create(endPoint); 52 | connection.ConnectAsync().Wait(); 53 | return new EventStoreWriter(connection); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/Common/IEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace AsyncOrigoSpike 5 | { 6 | /// 7 | /// Core transaction handling capabilities of the OrigoDB engine 8 | /// 9 | /// 10 | public interface IEngine : IDisposable 11 | { 12 | Task ExecuteAsync(Command command); 13 | Task ExecuteAsync(Command command); 14 | Task ExecuteAsync(Query query); 15 | 16 | R Execute(Command command); 17 | void Execute(Command command); 18 | R Execute(Query query); 19 | } 20 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/Common/IJournalWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace AsyncOrigoSpike 6 | { 7 | public interface IJournalWriter : IDisposable 8 | { 9 | Task AppendAsync(IEnumerable commands); 10 | } 11 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/Common/Kernel.cs: -------------------------------------------------------------------------------- 1 | namespace AsyncOrigoSpike 2 | { 3 | 4 | /// 5 | /// Encapsulates the in-memory object graph, 6 | /// executes commands and queries 7 | /// 8 | public class Kernel 9 | { 10 | readonly object _model; 11 | 12 | public Kernel(object model) 13 | { 14 | _model = model; 15 | } 16 | 17 | public object Execute(Command command) 18 | { 19 | return command.ExecuteImpl(_model); 20 | } 21 | 22 | public object Execute(Query query) 23 | { 24 | return query.ExecuteImpl(_model); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/Common/NullJournalWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace AsyncOrigoSpike 5 | { 6 | 7 | /// 8 | /// For baseline comparison of internal performance 9 | /// 10 | public class NullJournalWriter : IJournalWriter 11 | { 12 | public Task AppendAsync(IEnumerable commands) 13 | { 14 | return Task.FromResult(0); 15 | } 16 | 17 | public void Dispose() 18 | { 19 | 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/Common/TransactionPrimitives.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | 4 | namespace AsyncOrigoSpike 5 | { 6 | [Serializable] 7 | public abstract class Command : Command 8 | { 9 | public abstract R Execute(M model); 10 | 11 | public override object ExecuteImpl(object model) 12 | { 13 | return Execute((M) model); 14 | } 15 | } 16 | 17 | [Serializable] 18 | public abstract class Command : Command 19 | { 20 | public abstract void Execute(M model); 21 | public override object ExecuteImpl(object model) 22 | { 23 | Execute((M) model); 24 | return null; 25 | } 26 | } 27 | 28 | 29 | [Serializable] 30 | public abstract class Query : Query 31 | { 32 | public abstract R Execute(M model); 33 | public override object ExecuteImpl(object model) 34 | { 35 | return Execute((M)model); 36 | } 37 | } 38 | 39 | [Serializable] 40 | public abstract class Query 41 | { 42 | public abstract object ExecuteImpl(object model); 43 | } 44 | 45 | [Serializable] 46 | public abstract class Command 47 | { 48 | public abstract object ExecuteImpl(object model); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/DisruptorNet/CommandDispatcher.cs: -------------------------------------------------------------------------------- 1 | using Disruptor; 2 | using Disruptor.Dsl; 3 | 4 | namespace AsyncOrigoSpike 5 | { 6 | 7 | /// 8 | /// pushes journaled commands to the execution ring 9 | /// 10 | public class CommandDispatcher : IEventHandler 11 | { 12 | 13 | private readonly Disruptor _executionBuffer; 14 | 15 | public CommandDispatcher(Disruptor executionBuffer) 16 | { 17 | _executionBuffer = executionBuffer; 18 | } 19 | 20 | public void OnNext(Request data, long sequence, bool endOfBatch) 21 | { 22 | _executionBuffer.PublishEvent((e,i) => 23 | { 24 | e.Transaction = data.Transaction; 25 | e.Response = data.Response; 26 | return e; 27 | }); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/DisruptorNet/DisruptorEngine.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Disruptor; 3 | using Disruptor.Dsl; 4 | 5 | namespace AsyncOrigoSpike 6 | { 7 | /// 8 | /// Engine implementation using disruptor.net 9 | /// Uses 2 ringbuffers, one for journaling/preprocessing and 10 | /// one for command/query execution 11 | /// 12 | public class DisruptorEngine : IEngine 13 | { 14 | //batches and writes to the journal 15 | readonly Disruptor _commandJournaler; 16 | 17 | //executes commands and queries 18 | readonly Disruptor _transactionHandler; 19 | 20 | readonly IJournalWriter _journalWriter; 21 | 22 | //wire up the disruptor ring buffers and handlers 23 | public DisruptorEngine(M model, int batchSize, IJournalWriter journalWriter) 24 | { 25 | _journalWriter = journalWriter; 26 | var kernel = new Kernel(model); 27 | 28 | _transactionHandler = new Disruptor( 29 | () => new Request(), 30 | new MultiThreadedClaimStrategy(1024 * 64), 31 | new YieldingWaitStrategy(), 32 | TaskScheduler.Default); 33 | 34 | _commandJournaler = new Disruptor( 35 | () => new Request(), 36 | new MultiThreadedClaimStrategy(1024 * 64), 37 | new YieldingWaitStrategy(), 38 | TaskScheduler.Default); 39 | 40 | _transactionHandler.HandleEventsWith(new SerializedTransactionHandler(kernel)); 41 | 42 | _commandJournaler.HandleEventsWith(new Journaler(_journalWriter, batchSize)) 43 | .Then(new CommandDispatcher(_transactionHandler)); 44 | 45 | _transactionHandler.Start(); 46 | _commandJournaler.Start(); 47 | 48 | } 49 | 50 | public async Task ExecuteAsync(Command command) 51 | { 52 | var completion = new TaskCompletionSource(); 53 | 54 | _commandJournaler.PublishEvent((e, i) => 55 | { 56 | e.Transaction = command; 57 | e.Response = completion; 58 | return e; 59 | }); 60 | return (R)await completion.Task; 61 | } 62 | 63 | public Task ExecuteAsync(Command command) 64 | { 65 | var completion = new TaskCompletionSource(); 66 | _commandJournaler.PublishEvent((e, i) => 67 | { 68 | e.Transaction = command; 69 | e.Response = completion; 70 | return e; 71 | }); 72 | return completion.Task; 73 | 74 | } 75 | 76 | public async Task ExecuteAsync(Query query) 77 | { 78 | var completion = new TaskCompletionSource(); 79 | 80 | _transactionHandler.PublishEvent((e, i) => 81 | { 82 | e.Transaction = query; 83 | e.Response = completion; 84 | return e; 85 | }); 86 | return (R)await completion.Task; 87 | } 88 | 89 | public R Execute(Command command) 90 | { 91 | return ExecuteAsync(command).Result; 92 | } 93 | 94 | public R Execute(Query query) 95 | { 96 | return ExecuteAsync(query).Result; 97 | } 98 | 99 | public void Execute(Command command) 100 | { 101 | ExecuteAsync(command).Wait(); 102 | } 103 | 104 | public void Dispose() 105 | { 106 | _commandJournaler.Shutdown(); 107 | _transactionHandler.Shutdown(); 108 | _journalWriter.Dispose(); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/DisruptorNet/Journaler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Disruptor; 3 | 4 | namespace AsyncOrigoSpike 5 | { 6 | public class Journaler : IEventHandler 7 | { 8 | public const int DefaultBufferSize = 2048; 9 | 10 | readonly Request[] _buffer; 11 | 12 | //number of items in the buffer 13 | int _bufferedRequests; 14 | 15 | readonly IJournalWriter _journal; 16 | 17 | public Journaler(IJournalWriter journal, int bufferSize = DefaultBufferSize) 18 | { 19 | _journal = journal; 20 | _buffer = new Request[bufferSize]; 21 | } 22 | 23 | public void OnNext(Request data, long sequence, bool endOfBatch) 24 | { 25 | if (_bufferedRequests == _buffer.Length) Flush(); 26 | _buffer[_bufferedRequests++] = data; 27 | if (endOfBatch) Flush(); 28 | } 29 | 30 | /// 31 | /// Send the contents of the buffer to the 32 | /// journal and wait for the response 33 | /// 34 | private void Flush() 35 | { 36 | var commands = _buffer 37 | .Take(_bufferedRequests) 38 | .Select(e => e.Transaction as Command); 39 | 40 | _journal.AppendAsync(commands).Wait(); 41 | _bufferedRequests = 0; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/DisruptorNet/Request.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace AsyncOrigoSpike 4 | { 5 | public class Request 6 | { 7 | //either a query or a command 8 | public object Transaction; 9 | public TaskCompletionSource Response; 10 | } 11 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/DisruptorNet/SerializedTransactionHandler.cs: -------------------------------------------------------------------------------- 1 | using Disruptor; 2 | 3 | namespace AsyncOrigoSpike 4 | { 5 | /// 6 | /// Executes transactions one at a time 7 | /// 8 | public class SerializedTransactionHandler : IEventHandler 9 | { 10 | readonly Kernel _kernel; 11 | 12 | public SerializedTransactionHandler(Kernel kernel) 13 | { 14 | _kernel = kernel; 15 | } 16 | public void OnNext(Request data, long sequence, bool endOfBatch) 17 | { 18 | object result = null; 19 | if (data.Transaction is Command) 20 | { 21 | result = _kernel.Execute(data.Transaction as Command); 22 | } 23 | else 24 | { 25 | result = _kernel.Execute(data.Transaction as Query); 26 | } 27 | data.Response.SetResult(result); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AckAck")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AckAck")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("7c570bbb-788b-4f9e-b90b-f20a069f97bf")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/TplDataflow/CommandRequest.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Threading.Tasks.Dataflow; 3 | 4 | namespace AsyncOrigoSpike 5 | { 6 | public sealed class CommandRequest 7 | { 8 | public readonly Command Command; 9 | public readonly WriteOnceBlock Response; 10 | 11 | public CommandRequest(Command command, WriteOnceBlock response) 12 | { 13 | Command = command; 14 | Response = response; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/TplDataflow/ExecutionPipeline.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Threading.Tasks.Dataflow; 6 | 7 | namespace AsyncOrigoSpike 8 | { 9 | /// 10 | /// Schedules and executes transactions 11 | /// 12 | /// 13 | public class ExecutionPipeline 14 | { 15 | readonly Kernel _kernel; 16 | readonly BufferBlock _commandQueue; 17 | 18 | /// 19 | /// groups queries into batches for parallel processing 20 | /// 21 | readonly BatchBlock _queryQueue; 22 | 23 | /// 24 | /// Triggers a batch from the queryqueue at given intervals 25 | /// 26 | readonly Timer _timer; 27 | 28 | /// 29 | /// Maximum latency for queries 30 | /// 31 | public TimeSpan Interval = TimeSpan.FromMilliseconds(1); 32 | 33 | public int MaxConcurrentQueries = 4; 34 | 35 | public ExecutionPipeline(Kernel kernel) 36 | { 37 | _kernel = kernel; 38 | _commandQueue = new BufferBlock(); 39 | _queryQueue = new BatchBlock(MaxConcurrentQueries); 40 | 41 | var transactionHandler = new ActionBlock(t => 42 | { 43 | if (t is QueryRequest[]) 44 | { 45 | var queries = t as QueryRequest[]; 46 | Task[] tasks = queries.Select(q => Task.Factory.StartNew(_ => ExecuteQuery(q), null)).ToArray(); 47 | Task.WaitAll(tasks); 48 | } 49 | else if (t is CommandRequest[]) 50 | { 51 | var commands = t as CommandRequest[]; 52 | foreach (var commandContext in commands) 53 | { 54 | var result = _kernel.Execute(commandContext.Command); 55 | commandContext.Response.Post(result); 56 | } 57 | } 58 | 59 | }); 60 | _commandQueue.LinkTo(transactionHandler); 61 | _queryQueue.LinkTo(transactionHandler); 62 | _timer = new Timer(_ => _queryQueue.TriggerBatch()); 63 | _timer.Change(Interval, Interval); 64 | } 65 | 66 | void ExecuteQuery(QueryRequest context) 67 | { 68 | var result = _kernel.Execute(context.Query); 69 | context.Response.Post(result); 70 | } 71 | 72 | //accept a batch of commands to be executed 73 | internal void Post(CommandRequest[] commands) 74 | { 75 | _commandQueue.Post(commands); 76 | } 77 | 78 | /// 79 | /// accept a query to be executed 80 | /// 81 | /// 82 | internal void Post(QueryRequest query) 83 | { 84 | _queryQueue.Post(query); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/TplDataflow/QueryRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks.Dataflow; 2 | 3 | namespace AsyncOrigoSpike 4 | { 5 | internal class QueryRequest 6 | { 7 | public readonly Query Query; 8 | public readonly WriteOnceBlock Response; 9 | 10 | public QueryRequest(Query query, WriteOnceBlock responseBlock) 11 | { 12 | Response = responseBlock; 13 | Query = query; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/TplDataflow/TplBatchingJournaler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks.Dataflow; 6 | 7 | 8 | namespace AsyncOrigoSpike 9 | { 10 | 11 | public class TplBatchingJournaler : IDisposable 12 | { 13 | 14 | private readonly IJournalWriter _journalWriter; 15 | private Timer _timer; 16 | 17 | //commands start here 18 | private readonly BatchBlock _requestQueue; 19 | 20 | //then go here at given intervals or when the batch size is reached 21 | private ActionBlock _writerBlock; 22 | 23 | //after journaling, commands are passed to the dispatcher for scheduling 24 | private readonly ExecutionPipeline _dispatcher; 25 | 26 | //profiling stuff 27 | private List _batchSizes = new List(); 28 | private int _timerInvocations = 0; 29 | 30 | 31 | public TimeSpan Interval { get; set; } 32 | 33 | public TplBatchingJournaler(IJournalWriter journalWriter, ExecutionPipeline dispatcher, int batchSize) 34 | { 35 | Interval = TimeSpan.FromMilliseconds(16); 36 | _journalWriter = journalWriter; 37 | _dispatcher = dispatcher; 38 | 39 | _writerBlock = new ActionBlock(batch => Go(batch)); 40 | 41 | _requestQueue = new BatchBlock(batchSize); 42 | _requestQueue.LinkTo(_writerBlock); 43 | 44 | } 45 | 46 | 47 | private void OnTimerTick(object state) 48 | { 49 | _requestQueue.TriggerBatch(); 50 | _timerInvocations++; 51 | SetTimer(); 52 | } 53 | 54 | private void SetTimer() 55 | { 56 | _timer.Change(Interval, TimeSpan.FromMilliseconds(-1)); 57 | } 58 | 59 | public void Post(CommandRequest request) 60 | { 61 | if (_timer == null) 62 | { 63 | _timer = new Timer(OnTimerTick); 64 | SetTimer(); 65 | } 66 | _requestQueue.Post(request); 67 | } 68 | 69 | private void Go(CommandRequest[] batch) 70 | { 71 | _batchSizes.Add(batch.Length); 72 | _journalWriter.AppendAsync(batch.Select(ctx => ctx.Command)) 73 | .ContinueWith(t => _dispatcher.Post(batch)); 74 | SetTimer(); 75 | } 76 | 77 | public void Dispose() 78 | { 79 | Console.WriteLine("Timer invocations:" + _timerInvocations); 80 | var histogram = new SortedDictionary(); 81 | foreach (var count in _batchSizes) 82 | { 83 | if (!histogram.ContainsKey(count)) histogram[count] = 1; 84 | else histogram[count]++; 85 | } 86 | foreach (var key in histogram.Keys) 87 | { 88 | Console.WriteLine(key + ": " + histogram[key]); 89 | } 90 | _journalWriter.Dispose(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/TplDataflow/TplDataflowEngine.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using System.Threading.Tasks.Dataflow; 3 | 4 | namespace AsyncOrigoSpike 5 | { 6 | /// 7 | /// IEngine implementation using the TPL Dataflow library 8 | /// 9 | public class TplDataflowEngine : IEngine 10 | { 11 | 12 | readonly TplBatchingJournaler _journaler; 13 | readonly ExecutionPipeline _executionPipeline; 14 | 15 | public TplDataflowEngine(M model, int batchSize, IJournalWriter journalWriter) 16 | { 17 | var kernel = new Kernel(model); 18 | _executionPipeline = new ExecutionPipeline(kernel); 19 | _journaler = new TplBatchingJournaler(journalWriter, _executionPipeline, batchSize); 20 | 21 | } 22 | 23 | public async Task ExecuteAsync(Command command) 24 | { 25 | var response = new WriteOnceBlock(r => r); 26 | _journaler.Post(new CommandRequest(command, response)); 27 | return (R) await response.ReceiveAsync(); 28 | } 29 | 30 | public Task ExecuteAsync(Command command) 31 | { 32 | var response = new WriteOnceBlock(b => b); 33 | _journaler.Post(new CommandRequest(command, response)); 34 | return response.ReceiveAsync(); 35 | } 36 | 37 | public async Task ExecuteAsync(Query query) 38 | { 39 | var response = new WriteOnceBlock(r => r); 40 | _executionPipeline.Post(new QueryRequest(query, response)); 41 | return (R)await response.ReceiveAsync(); 42 | } 43 | 44 | public R Execute(Command command) 45 | { 46 | return ExecuteAsync(command).Result; 47 | } 48 | 49 | public R Execute(Query query) 50 | { 51 | return ExecuteAsync(query).Result; 52 | } 53 | 54 | public void Execute(Command command) 55 | { 56 | ExecuteAsync(command).Wait(); 57 | } 58 | 59 | public void Dispose() 60 | { 61 | _journaler.Dispose(); 62 | } 63 | 64 | } 65 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/TplNet/TplNetEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace AsyncOrigoSpike.TplNet 5 | { 6 | public class TplNetEngine : IEngine 7 | { 8 | readonly TplNetExecutor _executor; 9 | readonly TplNetJournaler _journaler; 10 | 11 | 12 | public TplNetEngine(M model, int batchSize, IJournalWriter journalWriter) 13 | { 14 | var kernel = new Kernel(model); 15 | _executor = new TplNetExecutor(kernel); 16 | _journaler = new TplNetJournaler(batchSize, journalWriter, _executor); 17 | 18 | _journaler.Start(); 19 | _executor.Start(); 20 | } 21 | 22 | public void Dispose() 23 | { 24 | _journaler.Stop(); 25 | _executor.Stop(); 26 | 27 | } 28 | 29 | public async Task ExecuteAsync(Command command) 30 | { 31 | var request = new Request {Response = new TaskCompletionSource(), Transaction = command}; 32 | _journaler.Push(request); 33 | return (R) await request.Response.Task; 34 | } 35 | 36 | public Task ExecuteAsync(Command command) 37 | { 38 | var request = new Request { Response = new TaskCompletionSource(), Transaction = command }; 39 | _journaler.Push(request); 40 | return request.Response.Task; 41 | } 42 | 43 | public async Task ExecuteAsync(Query query) 44 | { 45 | var request = new Request { Response = new TaskCompletionSource(), Transaction = query }; 46 | _executor.Push(request); 47 | return (R) await request.Response.Task; 48 | 49 | } 50 | 51 | public R Execute(Command command) 52 | { 53 | return ExecuteAsync(command).Result; 54 | } 55 | 56 | public void Execute(Command command) 57 | { 58 | ExecuteAsync(command).Wait(); 59 | } 60 | 61 | public R Execute(Query query) 62 | { 63 | return ExecuteAsync(query).Result; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/TplNet/TplNetExecutor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading.Tasks; 4 | 5 | namespace AsyncOrigoSpike.TplNet 6 | { 7 | public class TplNetExecutor 8 | { 9 | BlockingCollection _requestQueue = new BlockingCollection(); 10 | readonly Kernel _kernel; 11 | 12 | public TplNetExecutor(Kernel kernel) 13 | { 14 | _kernel = kernel; 15 | } 16 | 17 | public void Push(Request request) 18 | { 19 | _requestQueue.Add(request); 20 | } 21 | 22 | public void Start() 23 | { 24 | Task.Factory.StartNew(QueueConsumer); 25 | } 26 | 27 | private void QueueConsumer() 28 | { 29 | while (true) 30 | { 31 | if (_requestQueue.IsCompleted) break; 32 | var request = _requestQueue.Take(); 33 | try 34 | { 35 | Object result; 36 | if (request.Transaction is Command) 37 | { 38 | result = _kernel.Execute((Command) request.Transaction); 39 | } 40 | else 41 | { 42 | result = _kernel.Execute((Query)request.Transaction); 43 | } 44 | request.Response.SetResult(result); 45 | } 46 | catch (Exception e) 47 | { 48 | request.Response.SetException(e); 49 | } 50 | } 51 | } 52 | 53 | public void Stop() 54 | { 55 | _requestQueue.CompleteAdding(); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/TplNet/TplNetJournaler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace AsyncOrigoSpike.TplNet 8 | { 9 | public class TplNetJournaler 10 | { 11 | readonly BlockingCollection _commandQueue = new BlockingCollection(); 12 | readonly IJournalWriter _journalWriter; 13 | readonly int _batchSize; 14 | readonly TplNetExecutor _executor; 15 | 16 | 17 | public void Push(Request request) 18 | { 19 | _commandQueue.Add(request); 20 | } 21 | 22 | public void Start() 23 | { 24 | Task.Factory.StartNew(QueueConsumer); 25 | } 26 | 27 | public TplNetJournaler(int batchSize, IJournalWriter journalWriter, TplNetExecutor executor) 28 | { 29 | _batchSize = batchSize; 30 | _journalWriter = journalWriter; 31 | _executor = executor; 32 | } 33 | 34 | private void QueueConsumer() 35 | { 36 | var buf = new List(10000); 37 | while (true) 38 | { 39 | Request request; 40 | if (_commandQueue.IsCompleted) break; 41 | //wait for a first item 42 | if (!_commandQueue.TryTake(out request, TimeSpan.FromSeconds(1))) continue; 43 | buf.Add(request); 44 | 45 | //take the rest but don't wait 46 | while (buf.Count < _batchSize && _commandQueue.TryTake(out request)) 47 | { 48 | buf.Add(request); 49 | } 50 | 51 | //at this point we have at least one request to process 52 | var requests = buf.ToArray(); 53 | buf.Clear(); 54 | var commands = requests.Select(r => (Command) r.Transaction); 55 | 56 | _journalWriter.AppendAsync(commands).ContinueWith(t => 57 | { 58 | foreach (var r in requests) _executor.Push(r); 59 | }); 60 | 61 | } 62 | } 63 | 64 | public void Stop() 65 | { 66 | _commandQueue.CompleteAdding(); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Test/AsyncOrigoSpike.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {9A85CE33-5324-4F1C-8F3E-41B2752490CC} 8 | Library 9 | Properties 10 | AsyncOrigoSpike 11 | AsyncOrigoSpike.Test 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\Akka.0.6.3\lib\net45\Akka.dll 35 | 36 | 37 | False 38 | ..\packages\EventStore.Client.3.0.0\lib\net40\EventStore.ClientAPI.dll 39 | 40 | 41 | ..\packages\Metrics.NET.0.2.8\lib\net45\Metrics.dll 42 | 43 | 44 | False 45 | ..\packages\Newtonsoft.Json.6.0.1\lib\net45\Newtonsoft.Json.dll 46 | 47 | 48 | ..\packages\NUnit.2.6.3\lib\nunit.framework.dll 49 | 50 | 51 | 52 | 53 | False 54 | ..\packages\Microsoft.Tpl.Dataflow.4.5.23\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | {E0C71B98-17AF-4E95-ABC3-E8D31C631696} 76 | AsyncOrigoSpike.Core 77 | 78 | 79 | 80 | 87 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AckAck.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AckAck.Test")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e394b8af-5cb4-41a6-a67a-1ad2e7fa7ee4")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Test/TestModel/AddItemCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace AsyncOrigoSpike.Test 5 | { 6 | [Serializable] 7 | public class AddItemCommand : Command, int> 8 | { 9 | public readonly string Item; 10 | 11 | public AddItemCommand(string item) 12 | { 13 | Item = item; 14 | } 15 | 16 | public override int Execute(List model) 17 | { 18 | model.Add(Item); 19 | return model.Count; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Test/TestModel/GetSumQuery.cs: -------------------------------------------------------------------------------- 1 | namespace AsyncOrigoSpike.Test 2 | { 3 | public class GetSumQuery : Query 4 | { 5 | 6 | public override int Execute(IntModel model) 7 | { 8 | return model.Value; 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Test/TestModel/IntModel.cs: -------------------------------------------------------------------------------- 1 | namespace AsyncOrigoSpike.Test 2 | { 3 | public class IntModel 4 | { 5 | 6 | public int Value; 7 | } 8 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Test/TestModel/SumCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AsyncOrigoSpike.Test 4 | { 5 | [Serializable] 6 | public class SumCommand : Command 7 | { 8 | public readonly int Operand; 9 | public SumCommand(int i) 10 | { 11 | Operand = i; 12 | } 13 | 14 | public override int Execute(IntModel model) 15 | { 16 | model.Value += Operand; 17 | return Operand; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /AsyncOrigoSpike.Test/TheTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using AsyncOrigoSpike.TplNet; 7 | using NUnit.Framework; 8 | 9 | namespace AsyncOrigoSpike.Test 10 | { 11 | [TestFixture] 12 | public class TheTests 13 | { 14 | public const int CommandsPerRun = 20000; 15 | 16 | [Test,TestCaseSource("ProgressiveBatchSizes")] 17 | public void TimedBatchCommandRun(int batchSize) 18 | { 19 | foreach (var engine in CreateEngines>(batchSize)) 20 | { 21 | Console.WriteLine( engine.GetType().Name); 22 | Console.WriteLine("# commands: " + CommandsPerRun); 23 | Console.WriteLine("# commands per batch: " + batchSize); 24 | 25 | var sw = new Stopwatch(); 26 | sw.Start(); 27 | var tasks = Enumerable 28 | .Range(0, CommandsPerRun) 29 | .Select(i => engine.ExecuteAsync(new AddItemCommand(i.ToString()))).ToArray(); 30 | Task.WaitAll(tasks); 31 | sw.Stop(); 32 | Console.WriteLine("time elapsed: " + sw.Elapsed); 33 | Console.WriteLine("tps: " + CommandsPerRun/sw.Elapsed.TotalSeconds); 34 | Console.WriteLine(); 35 | engine.Dispose(); 36 | } 37 | } 38 | 39 | [Test] 40 | public void SingleEventPerEventStoreCall() 41 | { 42 | var journalWriter = EventStoreWriter.Create(); 43 | var sw = new Stopwatch(); 44 | Command[] commands = new Command[1]; 45 | sw.Start(); 46 | var tasks = Enumerable 47 | .Range(0, 10000) 48 | .Select(i => journalWriter.AppendAsync(commands)).ToArray(); 49 | Task.WaitAll(tasks); 50 | sw.Stop(); 51 | Console.WriteLine("elapsed: " + sw.Elapsed); 52 | journalWriter.Dispose(); 53 | } 54 | 55 | public IEnumerable ProgressiveBatchSizes() 56 | { 57 | return Enumerable.Range(0, 10).Select(i => 10 * (int)Math.Pow(2, i)); 58 | } 59 | 60 | private IEnumerable> CreateEngines(int batchSize) where M : new() 61 | { 62 | yield return new TplNetEngine(new M(), batchSize, EventStoreWriter.Create()); 63 | yield return new AkkaEngine(new M(), batchSize, EventStoreWriter.Create()); 64 | yield return new TplDataflowEngine(new M(), batchSize, EventStoreWriter.Create()); 65 | yield return new DisruptorEngine(new M(), batchSize, EventStoreWriter.Create()); 66 | 67 | } 68 | 69 | private IEnumerable> CreateCorrectnessEngines() 70 | { 71 | return CreateEngines(100); 72 | } 73 | 74 | [Test] 75 | public void CorrectnessTest() 76 | { 77 | foreach (var engine in CreateCorrectnessEngines()) 78 | using(engine) 79 | { 80 | var sum = 0; 81 | var tasks = new List(); 82 | for (int i = 1; i <= CommandsPerRun; i++) 83 | { 84 | sum += i; 85 | var command = new SumCommand(i); 86 | tasks.Add( 87 | engine.ExecuteAsync(command) 88 | .ContinueWith(t => Assert.AreEqual(t.Result, command.Operand))); 89 | } 90 | Task.WaitAll(tasks.ToArray()); 91 | int result = engine.ExecuteAsync(new GetSumQuery()).Result; 92 | Assert.AreEqual(result, sum); 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AsyncOrigoSpike.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30501.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncOrigoSpike.Core", "AsyncOrigoSpike.Core\AsyncOrigoSpike.Core.csproj", "{E0C71B98-17AF-4E95-ABC3-E8D31C631696}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncOrigoSpike.Test", "AsyncOrigoSpike.Test\AsyncOrigoSpike.Test.csproj", "{9A85CE33-5324-4F1C-8F3E-41B2752490CC}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {E0C71B98-17AF-4E95-ABC3-E8D31C631696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {E0C71B98-17AF-4E95-ABC3-E8D31C631696}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {E0C71B98-17AF-4E95-ABC3-E8D31C631696}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {E0C71B98-17AF-4E95-ABC3-E8D31C631696}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {9A85CE33-5324-4F1C-8F3E-41B2752490CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {9A85CE33-5324-4F1C-8F3E-41B2752490CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {9A85CE33-5324-4F1C-8F3E-41B2752490CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {9A85CE33-5324-4F1C-8F3E-41B2752490CC}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Devrex Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # OrigoDB is going async 3 | 4 | This solution compares 4 different implementations: 5 | 6 | * Actors model using Akka.net 7 | * Semi actors model using the TPL Dataflow library 8 | * Disruptor.NET - the NET port of the LMAX disruptor 9 | * Simple TPL and ConcurrentQueues 10 | 11 | --------------------------------------------------------------------------------