diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..92314f0e6c2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://EditorConfig.org + +root = true + +[*] +end_of_line = CRLF + +[*.cs] +indent_style = tab \ No newline at end of file diff --git a/.gitignore b/.gitignore index 44b66957b34..abd2a25c953 100644 --- a/.gitignore +++ b/.gitignore @@ -191,6 +191,10 @@ UpgradeLog*.htm *.mdf *.ldf +# make exception for Akka.Persistence.SqlServer database file +!AkkaPersistenceSqlServerSpecDb.mdf +!AkkaPersistenceSqlServerSpecDb_log.ldf + # Business Intelligence projects *.rdl.data *.bim.layout diff --git a/README.md b/README.md index d86257eb3e8..de3d8485547 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,16 @@ [![Issue Stats](http://www.issuestats.com/github/akkadotnet/akka.net/badge/pr)](http://www.issuestats.com/github/akkadotnet/akka.net) [![Issue Stats](http://www.issuestats.com/github/akkadotnet/akka.net/badge/issue)](http://www.issuestats.com/github/akkadotnet/akka.net) -**Akka.NET** is a port of the popular Java/Scala framework Akka to .NET. - -This is a community driven port and is not affiliated with Typesafe who makes the original Java/Scala version. +**Akka.NET** is a community-driven port of the popular Java/Scala framework [Akka](http://akka.io) to .NET. * Subscribe to the Akka.NET dev feed: https://twitter.com/AkkaDotNet (@AkkaDotNet) * Support forum: https://groups.google.com/forum/#!forum/akkadotnet-user-list -* Mail: akkadotnet@gmail.com -* Stackoverflow: http://stackoverflow.com/questions/tagged/akka.net - -## BETA -Please note that Akka.NET is currently in beta. Some features are missing, and we have a few known bugs. -You can follow our work towards version 1 here: https://github.com/akkadotnet/akka.net/milestones/Version%201 +* Mail: hi@getakka.net +* Stack Overflow: http://stackoverflow.com/questions/tagged/akka.net ### Documentation and resources -#### [akkadotnet.github.io](http://akkadotnet.github.io) +#### [Akka.NET Community SIte](http://getakka.net) ### Install Akka.NET via NuGet diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ae012cbbb50..7146021d351 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,32 @@ +#### 1.0.1 Apr 28 2015 + +**Bugfix release for Akka.NET v1.0.** + +Fixes: +* [v1.0 F# scheduling API not sending any scheduled messages](https://github.com/akkadotnet/akka.net/issues/831) +* [PinnedDispatcher - uses single thread for all actors instead of creating persanal thread for every actor](https://github.com/akkadotnet/akka.net/issues/850) +* [Hotfix async await when awaiting IO completion port based tasks](https://github.com/akkadotnet/akka.net/pull/843) +* [Fix for async await suspend-resume mechanics](https://github.com/akkadotnet/akka.net/pull/836) +* [Nested Ask async await causes null-pointer exception in ActorTaskScheduler](https://github.com/akkadotnet/akka.net/issues/855) +* [Akka.Remote: can't reply back remotely to child of Pool router](https://github.com/akkadotnet/akka.net/issues/884) +* [Context.DI().ActorOf shouldn't require a parameterless constructor](https://github.com/akkadotnet/akka.net/issues/832) +* [DIActorContextAdapter uses typeof().Name instead of AssemblyQualifiedName](https://github.com/akkadotnet/akka.net/issues/833) +* [IndexOutOfRangeException with RoundRobinRoutingLogic & SmallestMailboxRoutingLogic](https://github.com/akkadotnet/akka.net/issues/908) + +New Features: + +**Akka.TestKit.NUnit** +Akka.NET now has support for [NUnit ](http://nunit.org/) inside its TestKit. You can install Akka.TestKit.NUnit via the NuGet commandline: + +``` +PM> Install-Package Akka.TestKit.NUnit +``` + +**Akka.Persistence.SqlServer** +The first full implementation of Akka.Persistence is now available for SQL Server. + +[Read the full instructions for working with Akka.Persistence.SQLServer here](https://github.com/akkadotnet/akka.net/tree/dev/src/contrib/persistence/Akka.Persistence.SqlServer). + #### 1.0.0 Apr 09 2015 **Akka.NET is officially no longer in beta status**. The APIs introduced in Akka.NET v1.0 will enjoy long-term support from the Akka.NET development team and all of its professional support partners. diff --git a/build.cmd b/build.cmd index 0ab64382b7f..76b0ef95e84 100644 --- a/build.cmd +++ b/build.cmd @@ -7,6 +7,7 @@ src\.nuget\NuGet.exe update -self src\.nuget\NuGet.exe install FAKE -OutputDirectory src\packages -ExcludeVersion -Version 3.4.1 src\.nuget\NuGet.exe install xunit.runners -OutputDirectory src\packages\FAKE -ExcludeVersion -Version 1.9.2 +src\.nuget\NuGet.exe install nunit.runners -OutputDirectory src\packages\FAKE -ExcludeVersion -Version 2.6.4 if not exist src\packages\SourceLink.Fake\tools\SourceLink.fsx ( src\.nuget\nuget.exe install SourceLink.Fake -OutputDirectory src\packages -ExcludeVersion diff --git a/build.fsx b/build.fsx index 4772b412aaa..9ac6cf7715d 100644 --- a/build.fsx +++ b/build.fsx @@ -7,6 +7,7 @@ open System.IO open Fake open Fake.FileUtils open Fake.MSTest +open Fake.NUnitCommon open Fake.TaskRunnerHelper open Fake.ProcessHelper @@ -94,7 +95,7 @@ Target "AssemblyInfo" <| fun _ -> Attribute.Copyright copyright Attribute.Trademark "" Attribute.Version version - Attribute.FileVersion version ] + Attribute.FileVersion version ] |> ignore //-------------------------------------------------------------------------------- // Build the solution @@ -180,6 +181,7 @@ Target "CopyOutput" <| fun _ -> "contrib/dependencyinjection/Akka.DI.CastleWindsor" "contrib/dependencyinjection/Akka.DI.Ninject" "contrib/testkits/Akka.TestKit.Xunit" + "contrib/testkits/Akka.TestKit.NUnit" ] |> List.iter copyOutput @@ -202,11 +204,20 @@ Target "CleanTests" <| fun _ -> open XUnitHelper Target "RunTests" <| fun _ -> let msTestAssemblies = !! "src/**/bin/Release/Akka.TestKit.VsTest.Tests.dll" - let xunitTestAssemblies = !! "src/**/bin/Release/*.Tests.dll" -- "src/**/bin/Release/Akka.TestKit.VsTest.Tests.dll" + let nunitTestAssemblies = !! "src/**/bin/Release/Akka.TestKit.NUnit.Tests.dll" + let xunitTestAssemblies = !! "src/**/bin/Release/*.Tests.dll" -- + "src/**/bin/Release/Akka.TestKit.VsTest.Tests.dll" -- + "src/**/bin/Release/Akka.TestKit.NUnit.Tests.dll" -- + "src/**/bin/Release/Akka.Persistence.SqlServer.Tests.dll" mkdir testOutput MSTest (fun p -> p) msTestAssemblies + nunitTestAssemblies + |> NUnit (fun p -> + {p with + DisableShadowCopy = true; + OutputFile = testOutput + @"\NUnitTestResults.xml"}) let xunitToolPath = findToolInSubPath "xunit.console.clr4.exe" "src/packages/xunit.runners*" printfn "Using XUnit runner: %s" xunitToolPath @@ -237,6 +248,14 @@ Target "MultiNodeTests" <| fun _ -> info.Arguments <- args) (System.TimeSpan.FromMinutes 60.0) (* This is a VERY long running task. *) if result <> 0 then failwithf "MultiNodeTestRunner failed. %s %s" multiNodeTestPath args +Target "RunSqlServerTests" <| fun _ -> + let sqlServerTests = !! "src/**/bin/Release/Akka.Persistence.SqlServer.Tests.dll" + let xunitToolPath = findToolInSubPath "xunit.console.clr4.exe" "src/packages/xunit.runners*" + printfn "Using XUnit runner: %s" xunitToolPath + xUnit + (fun p -> { p with OutputDir = testOutput; ToolPath = xunitToolPath }) + sqlServerTests + //-------------------------------------------------------------------------------- // Nuget targets //-------------------------------------------------------------------------------- diff --git a/build.sh b/build.sh index 794fd83df6a..044396ab440 100755 --- a/build.sh +++ b/build.sh @@ -14,6 +14,7 @@ mono $SCRIPT_PATH/src/.nuget/NuGet.exe update -self mono $SCRIPT_PATH/src/.nuget/NuGet.exe install FAKE -OutputDirectory $SCRIPT_PATH/src/packages -ExcludeVersion -Version 3.4.1 mono $SCRIPT_PATH/src/.nuget/NuGet.exe install xunit.runners -OutputDirectory $SCRIPT_PATH/src/packages/FAKE -ExcludeVersion -Version 1.9.2 +mono $SCRIPT_PATH/src/.nuget/NuGet.exe install nunit.runners -OutputDirectory $SCRIPT_PATH/src/packages/FAKE -ExcludeVersion -Version 2.6.4 if ! [ -e $SCRIPT_PATH/src/packages/SourceLink.Fake/tools/SourceLink.fsx ] ; then mono $SCRIPT_PATH/src/.nuget/NuGet.exe install SourceLink.Fake -OutputDirectory $SCRIPT_PATH/src/packages -ExcludeVersion diff --git a/src/Akka.sln b/src/Akka.sln index 9e7fd81ba91..3f8ddf238e8 100644 --- a/src/Akka.sln +++ b/src/Akka.sln @@ -184,6 +184,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasicUnityUses", "contrib\d EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.DI.Unity", "contrib\dependencyInjection\Akka.DI.Unity\Akka.DI.Unity.csproj", "{2065C3A2-8C15-4912-BCF5-AE89E3DDA079}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.NUnit", "contrib\testkits\Akka.TestKit.NUnit\Akka.TestKit.NUnit.csproj", "{BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.TestKit.NUnit.Tests", "contrib\testkits\Akka.TestKit.NUnit.Tests\Akka.TestKit.NUnit.Tests.csproj", "{D63223FA-03F5-4B32-A6EC-668F718C0826}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Persistence", "Persistence", "{264C22A4-CAFC-41F6-B82C-4DDC5C196767}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.SqlServer", "contrib\persistence\Akka.Persistence.SqlServer\Akka.Persistence.SqlServer.csproj", "{BAC85686-AFC4-413E-98DC-5ED8F468BC63}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.SqlServer.Tests", "contrib\persistence\Akka.Persistence.SqlServer.Tests\Akka.Persistence.SqlServer.Tests.csproj", "{5A3C24D7-0D1C-4974-BBB4-22AC792666DE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug Mono|Any CPU = Debug Mono|Any CPU @@ -661,6 +671,38 @@ Global {2065C3A2-8C15-4912-BCF5-AE89E3DDA079}.Release Mono|Any CPU.Build.0 = Release|Any CPU {2065C3A2-8C15-4912-BCF5-AE89E3DDA079}.Release|Any CPU.ActiveCfg = Release|Any CPU {2065C3A2-8C15-4912-BCF5-AE89E3DDA079}.Release|Any CPU.Build.0 = Release|Any CPU + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E}.Debug Mono|Any CPU.ActiveCfg = Debug|Any CPU + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E}.Debug Mono|Any CPU.Build.0 = Debug|Any CPU + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E}.Release|Any CPU.Build.0 = Release|Any CPU + {D63223FA-03F5-4B32-A6EC-668F718C0826}.Debug Mono|Any CPU.ActiveCfg = Debug|Any CPU + {D63223FA-03F5-4B32-A6EC-668F718C0826}.Debug Mono|Any CPU.Build.0 = Debug|Any CPU + {D63223FA-03F5-4B32-A6EC-668F718C0826}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D63223FA-03F5-4B32-A6EC-668F718C0826}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D63223FA-03F5-4B32-A6EC-668F718C0826}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {D63223FA-03F5-4B32-A6EC-668F718C0826}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {D63223FA-03F5-4B32-A6EC-668F718C0826}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D63223FA-03F5-4B32-A6EC-668F718C0826}.Release|Any CPU.Build.0 = Release|Any CPU + {BAC85686-AFC4-413E-98DC-5ED8F468BC63}.Debug Mono|Any CPU.ActiveCfg = Debug|Any CPU + {BAC85686-AFC4-413E-98DC-5ED8F468BC63}.Debug Mono|Any CPU.Build.0 = Debug|Any CPU + {BAC85686-AFC4-413E-98DC-5ED8F468BC63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAC85686-AFC4-413E-98DC-5ED8F468BC63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAC85686-AFC4-413E-98DC-5ED8F468BC63}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {BAC85686-AFC4-413E-98DC-5ED8F468BC63}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {BAC85686-AFC4-413E-98DC-5ED8F468BC63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAC85686-AFC4-413E-98DC-5ED8F468BC63}.Release|Any CPU.Build.0 = Release|Any CPU + {5A3C24D7-0D1C-4974-BBB4-22AC792666DE}.Debug Mono|Any CPU.ActiveCfg = Debug|Any CPU + {5A3C24D7-0D1C-4974-BBB4-22AC792666DE}.Debug Mono|Any CPU.Build.0 = Debug|Any CPU + {5A3C24D7-0D1C-4974-BBB4-22AC792666DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A3C24D7-0D1C-4974-BBB4-22AC792666DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A3C24D7-0D1C-4974-BBB4-22AC792666DE}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU + {5A3C24D7-0D1C-4974-BBB4-22AC792666DE}.Release Mono|Any CPU.Build.0 = Release|Any CPU + {5A3C24D7-0D1C-4974-BBB4-22AC792666DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A3C24D7-0D1C-4974-BBB4-22AC792666DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -742,5 +784,10 @@ Global {D1CCD86E-0EF8-473A-979B-25E1235FEA2D} = {B1D10183-8FAE-4506-B935-403FCED89BDB} {32EABA12-DDAA-4F2A-B254-85239267D869} = {D1CCD86E-0EF8-473A-979B-25E1235FEA2D} {2065C3A2-8C15-4912-BCF5-AE89E3DDA079} = {B1D10183-8FAE-4506-B935-403FCED89BDB} + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E} + {D63223FA-03F5-4B32-A6EC-668F718C0826} = {7625FD95-4B2C-4A5B-BDD5-94B1493FAC8E} + {264C22A4-CAFC-41F6-B82C-4DDC5C196767} = {588C1513-FAB6-42C3-B6FC-3485F13620CF} + {BAC85686-AFC4-413E-98DC-5ED8F468BC63} = {264C22A4-CAFC-41F6-B82C-4DDC5C196767} + {5A3C24D7-0D1C-4974-BBB4-22AC792666DE} = {264C22A4-CAFC-41F6-B82C-4DDC5C196767} EndGlobalSection EndGlobal diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index 2f8a918b588..7f7e44b208a 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -11,6 +11,6 @@ [assembly: AssemblyCompanyAttribute("Akka.NET Team")] [assembly: AssemblyCopyrightAttribute("Copyright © 2013-2015 Akka.NET Team")] [assembly: AssemblyTrademarkAttribute("")] -[assembly: AssemblyVersionAttribute("1.0.0.0")] -[assembly: AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: AssemblyVersionAttribute("1.0.1.0")] +[assembly: AssemblyFileVersionAttribute("1.0.1.0")] diff --git a/src/benchmark/PingPong/PingPong.csproj b/src/benchmark/PingPong/PingPong.csproj index 798ce81a289..f8419541041 100644 --- a/src/benchmark/PingPong/PingPong.csproj +++ b/src/benchmark/PingPong/PingPong.csproj @@ -60,12 +60,8 @@ - - - - - + diff --git a/src/contrib/dependencyInjection/Akka.DI.AutoFac/Akka.DI.AutoFac.csproj b/src/contrib/dependencyInjection/Akka.DI.AutoFac/Akka.DI.AutoFac.csproj index 16e3fdc9606..8629834eb56 100644 --- a/src/contrib/dependencyInjection/Akka.DI.AutoFac/Akka.DI.AutoFac.csproj +++ b/src/contrib/dependencyInjection/Akka.DI.AutoFac/Akka.DI.AutoFac.csproj @@ -41,11 +41,7 @@ - - - - diff --git a/src/contrib/dependencyInjection/Akka.DI.AutoFac/Akka.DI.AutoFac.nuspec b/src/contrib/dependencyInjection/Akka.DI.AutoFac/Akka.DI.AutoFac.nuspec index 0c3cfd6bb99..34ca4d62db9 100644 --- a/src/contrib/dependencyInjection/Akka.DI.AutoFac/Akka.DI.AutoFac.nuspec +++ b/src/contrib/dependencyInjection/Akka.DI.AutoFac/Akka.DI.AutoFac.nuspec @@ -9,7 +9,7 @@ AutoFac Dependency Injection (DI) support for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/contrib/dependencyInjection/Akka.DI.AutoFac/AutoFacDependencyResolver.cs b/src/contrib/dependencyInjection/Akka.DI.AutoFac/AutoFacDependencyResolver.cs index 2cf5b53c3c0..27aaa2016e2 100644 --- a/src/contrib/dependencyInjection/Akka.DI.AutoFac/AutoFacDependencyResolver.cs +++ b/src/contrib/dependencyInjection/Akka.DI.AutoFac/AutoFacDependencyResolver.cs @@ -8,20 +8,16 @@ using System; using System.Collections.Concurrent; using System.Linq; +using System.Runtime.CompilerServices; using Akka.Actor; -using System.Text; using Akka.DI.Core; -using System.Runtime.CompilerServices; using Autofac; -using System.Text; -using System.Runtime.CompilerServices; namespace Akka.DI.AutoFac { /// - /// Provide services to ActorSystem Extension system used to create Actor - /// using the AutoFac IOC Container to handle wiring up dependencies to - /// Actors + /// Provides services to the extension system + /// used to create actors using the AutoFac IoC container. /// public class AutoFacDependencyResolver : IDependencyResolver { @@ -31,15 +27,18 @@ public class AutoFacDependencyResolver : IDependencyResolver private ConditionalWeakTable references; /// - /// AutoFacDependencyResolver Constructor + /// Initializes a new instance of the class. /// - /// Instance to the AutoFac IContainer - /// Instance of the ActorSystem - public AutoFacDependencyResolver(ILifetimeScope rootScope, ActorSystem system) + /// The container used to resolve references + /// The actor system to plug into + /// + /// Either the or the was null. + /// + public AutoFacDependencyResolver(ILifetimeScope container, ActorSystem system) { if (system == null) throw new ArgumentNullException("system"); - if (rootScope == null) throw new ArgumentNullException("container"); - this.container = rootScope; + if (container == null) throw new ArgumentNullException("container"); + this.container = container; typeCache = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); this.system = system; this.system.AddDependencyResolver(this); @@ -47,13 +46,12 @@ public AutoFacDependencyResolver(ILifetimeScope rootScope, ActorSystem system) } /// - /// Returns the Type for the Actor Type specified in the actorName + /// Retrieves an actor's type with the specified name /// - /// - /// + /// The name of the actor to retrieve + /// The type with the specified actor name public Type GetType(string actorName) { - typeCache. TryAdd(actorName, actorName.GetTypeValue() ?? @@ -66,40 +64,39 @@ public Type GetType(string actorName) FirstOrDefault()); return typeCache[actorName]; - } + /// - /// Creates a delegate factory based on the actorName + /// Creates a delegate factory used to create actors based on their type /// - /// Name of the ActorType - /// factory delegate - public Func CreateActorFactory(string actorName) + /// The type of actor that the factory builds + /// A delegate factory used to create actors + public Func CreateActorFactory(Type actorType) { return () => { - Type actorType = this.GetType(actorName); var scope = container.BeginLifetimeScope(); var actor = (ActorBase)scope.Resolve(actorType); references.Add(actor, scope); return actor; }; } + /// - /// Used Register the Configuration for the ActorType specified in TActor + /// Used to register the configuration for an actor of the specified type /// - /// Tye of Actor that needs to be created - /// Props configuration instance + /// The type of actor the configuration is based + /// The configuration object for the given actor type public Props Create() where TActor : ActorBase { - return system.GetExtension().Props(typeof(TActor).Name); + return system.GetExtension().Props(typeof(TActor)); } /// - /// This method is used to signal the DI Container that it can - /// release it's reference to the actor. HERE + /// Signals the DI container to release it's reference to the actor. + /// HERE /// - /// - + /// The actor to remove from the container public void Release(ActorBase actor) { ILifetimeScope scope; @@ -111,24 +108,4 @@ public void Release(ActorBase actor) } } } - internal static class Extensions - { - public static Type GetTypeValue(this string typeName) - { - var firstTry = Type.GetType(typeName); - Func searchForType = () => - { - return - AppDomain. - CurrentDomain. - GetAssemblies(). - SelectMany(x => x.GetTypes()). - Where(t => t.Name.Equals(typeName)). - FirstOrDefault(); - }; - return firstTry ?? searchForType(); - } - - } } - diff --git a/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/Akka.DI.CastleWindsor.csproj b/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/Akka.DI.CastleWindsor.csproj index 3b712b0bf44..d8ae7d05c70 100644 --- a/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/Akka.DI.CastleWindsor.csproj +++ b/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/Akka.DI.CastleWindsor.csproj @@ -44,11 +44,7 @@ - - - - diff --git a/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/Akka.DI.CastleWindsor.nuspec b/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/Akka.DI.CastleWindsor.nuspec index 0211e3b1f38..535fa34aa09 100644 --- a/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/Akka.DI.CastleWindsor.nuspec +++ b/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/Akka.DI.CastleWindsor.nuspec @@ -9,7 +9,7 @@ CastleWindsor Dependency Injection (DI) support for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/WindsorDependencyResolver.cs b/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/WindsorDependencyResolver.cs index f9368c40747..e7ca19231eb 100644 --- a/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/WindsorDependencyResolver.cs +++ b/src/contrib/dependencyInjection/Akka.DI.CastleWindsor/WindsorDependencyResolver.cs @@ -15,20 +15,23 @@ namespace Akka.DI.CastleWindsor { /// - /// Provide services to ActorSystem Extension system used to create Actor - /// using the CastleWindsor IOC Container to handle wiring up dependencies to - /// Actors + /// Provides services to the extension system + /// used to create actors using the CastleWindsor IoC container. /// public class WindsorDependencyResolver : IDependencyResolver { private IWindsorContainer container; private ConcurrentDictionary typeCache; private ActorSystem system; + /// - /// WindsorDependencyResolver Constructor + /// Initializes a new instance of the class. /// - /// Instance of the WindsorContainer - /// Instance of the ActorSystem + /// The container used to resolve references + /// The actor system to plug into + /// + /// Either the or the was null. + /// public WindsorDependencyResolver(IWindsorContainer container, ActorSystem system) { if (system == null) throw new ArgumentNullException("system"); @@ -40,13 +43,12 @@ public WindsorDependencyResolver(IWindsorContainer container, ActorSystem system } /// - /// Returns the Type for the Actor Type specified in the actorName + /// Retrieves an actor's type with the specified name /// - /// - /// + /// The name of the actor to retrieve + /// The type with the specified actor name public Type GetType(string actorName) { - typeCache. TryAdd(actorName, actorName.GetTypeValue() ?? @@ -59,55 +61,35 @@ public Type GetType(string actorName) return typeCache[actorName]; } + /// - /// Creates a delegate factory based on the actorName + /// Creates a delegate factory used to create actors based on their type /// - /// Name of the ActorType - /// factory delegate - public Func CreateActorFactory(string actorName) + /// The type of actor that the factory builds + /// A delegate factory used to create actors + public Func CreateActorFactory(Type actorType) { - return () => (ActorBase)container.Resolve(GetType(actorName)); + return () => (ActorBase)container.Resolve(actorType); } + /// - /// Used Register the Configuration for the ActorType specified in TActor + /// Used to register the configuration for an actor of the specified type /// - /// Tye of Actor that needs to be created - /// Props configuration instance + /// The type of actor the configuration is based + /// The configuration object for the given actor type public Props Create() where TActor : ActorBase { - return system.GetExtension().Props(typeof(TActor).Name); + return system.GetExtension().Props(typeof(TActor)); } /// - /// This method is used to signal the DI Container that it can - /// release it's reference to the actor. HERE + /// Signals the DI container to release it's reference to the actor. + /// HERE /// - /// - + /// The actor to remove from the container public void Release(ActorBase actor) { this.container.Release(actor); } - - } - internal static class Extensions - { - public static Type GetTypeValue(this string typeName) - { - var firstTry = Type.GetType(typeName); - Func searchForType = () => - { - return - AppDomain. - CurrentDomain. - GetAssemblies(). - SelectMany(x => x.GetTypes()). - Where(t => t.Name.Equals(typeName)). - FirstOrDefault(); - }; - return firstTry ?? searchForType(); - } - } } - diff --git a/src/contrib/dependencyInjection/Akka.DI.Core/Akka.DI.Core.csproj b/src/contrib/dependencyInjection/Akka.DI.Core/Akka.DI.Core.csproj index c9e40b8f66e..8d61ce7db5c 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Core/Akka.DI.Core.csproj +++ b/src/contrib/dependencyInjection/Akka.DI.Core/Akka.DI.Core.csproj @@ -36,11 +36,7 @@ - - - - diff --git a/src/contrib/dependencyInjection/Akka.DI.Core/Akka.DI.Core.nuspec b/src/contrib/dependencyInjection/Akka.DI.Core/Akka.DI.Core.nuspec index 941140be9e6..5dc43e93d23 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Core/Akka.DI.Core.nuspec +++ b/src/contrib/dependencyInjection/Akka.DI.Core/Akka.DI.Core.nuspec @@ -9,7 +9,7 @@ Dependency injection support for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/contrib/dependencyInjection/Akka.DI.Core/DIActorContextAdapter.cs b/src/contrib/dependencyInjection/Akka.DI.Core/DIActorContextAdapter.cs index 01e607e0a48..db0765c01d8 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Core/DIActorContextAdapter.cs +++ b/src/contrib/dependencyInjection/Akka.DI.Core/DIActorContextAdapter.cs @@ -5,12 +5,8 @@ // //----------------------------------------------------------------------- -using Akka.Actor; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Akka.Actor; namespace Akka.DI.Core { @@ -24,9 +20,9 @@ public DIActorContextAdapter(IActorContext context) this.context = context; this.producer = context.System.GetExtension(); } - public IActorRef ActorOf(string name = null) where TActor : ActorBase, new() + public IActorRef ActorOf(string name = null) where TActor : ActorBase { - return context.ActorOf(producer.Props(typeof(TActor).Name), name); + return context.ActorOf(producer.Props(typeof(TActor)), name); } } } diff --git a/src/contrib/dependencyInjection/Akka.DI.Core/DIActorProducer.cs b/src/contrib/dependencyInjection/Akka.DI.Core/DIActorProducer.cs index 731ea42ebb1..8c5f66b93c5 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Core/DIActorProducer.cs +++ b/src/contrib/dependencyInjection/Akka.DI.Core/DIActorProducer.cs @@ -16,25 +16,26 @@ namespace Akka.DI.Core public class DIActorProducer : IIndirectActorProducer { private IDependencyResolver dependencyResolver; - private string actorName; + private Type actorType; + readonly Func actorFactory; public DIActorProducer(IDependencyResolver dependencyResolver, - string actorName) + Type actorType) { if (dependencyResolver == null) throw new ArgumentNullException("dependencyResolver"); - if (actorName == null) throw new ArgumentNullException("actorName"); + if (actorType == null) throw new ArgumentNullException("actorType"); this.dependencyResolver = dependencyResolver; - this.actorName = actorName; - this.actorFactory = dependencyResolver.CreateActorFactory(actorName); + this.actorType = actorType; + this.actorFactory = dependencyResolver.CreateActorFactory(actorType); } /// /// The System.Type of the Actor specified in the constructor parameter actorName /// public Type ActorType { - get { return this.dependencyResolver.GetType(this.actorName); } + get { return this.dependencyResolver.GetType(); } } /// /// Creates an instance of the Actor based on the Type specified in the constructor parameter actorName diff --git a/src/contrib/dependencyInjection/Akka.DI.Core/DIExt.cs b/src/contrib/dependencyInjection/Akka.DI.Core/DIExt.cs index 594a73dd6e6..ad6ec48b78b 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Core/DIExt.cs +++ b/src/contrib/dependencyInjection/Akka.DI.Core/DIExt.cs @@ -26,9 +26,9 @@ public void Initialize(IDependencyResolver dependencyResolver) if (dependencyResolver == null) throw new ArgumentNullException("dependencyResolver"); this.dependencyResolver = dependencyResolver; } - public Props Props(String actorName) + public Props Props(Type actorType) { - return new Props(typeof(DIActorProducer), new object[] { dependencyResolver, actorName }); + return new Props(typeof(DIActorProducer), new object[] { dependencyResolver, actorType }); } } diff --git a/src/contrib/dependencyInjection/Akka.DI.Core/Extensions.cs b/src/contrib/dependencyInjection/Akka.DI.Core/Extensions.cs index fb357744fc2..246a959ec85 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Core/Extensions.cs +++ b/src/contrib/dependencyInjection/Akka.DI.Core/Extensions.cs @@ -6,6 +6,7 @@ //----------------------------------------------------------------------- using System; +using System.Linq; using Akka.Actor; namespace Akka.DI.Core @@ -34,6 +35,17 @@ public static DIActorContextAdapter DI(this IActorContext context) return new DIActorContextAdapter(context); } + public static Type GetTypeValue(this string typeName) + { + var firstTry = Type.GetType(typeName); + Func searchForType = () => + AppDomain.CurrentDomain + .GetAssemblies() + .SelectMany(x => x.GetTypes()) + .FirstOrDefault(t => t.Name.Equals(typeName)); + + return firstTry ?? searchForType(); + } } } diff --git a/src/contrib/dependencyInjection/Akka.DI.Core/IDependencyResolver.cs b/src/contrib/dependencyInjection/Akka.DI.Core/IDependencyResolver.cs index 5d59158b2c9..be7f94ab037 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Core/IDependencyResolver.cs +++ b/src/contrib/dependencyInjection/Akka.DI.Core/IDependencyResolver.cs @@ -11,34 +11,33 @@ namespace Akka.DI.Core { /// - /// Contract used provide services to ActorSystem Extension system used to create - /// Actors + /// Defines services used by the extension system to create actors /// public interface IDependencyResolver { /// - /// Returns the Type for the Actor Type specified in the actorName + /// Retrieves an actor's type with the specified name /// - /// - /// Type of the Actor specified in the actorName + /// The name of the actor to retrieve + /// The type with the specified actor name Type GetType(string actorName); /// - /// Creates a delegate factory based on the actorName + /// Creates a delegate factory used to create actors based on their type /// - /// Name of the ActorType - /// factory delegate - Func CreateActorFactory(string actorName); + /// The type of actor that the factory builds + /// A delegate factory used to create actors + Func CreateActorFactory(Type actorType); /// - /// Used Register the Configuration for the ActorType specified in TActor + /// Used to register the configuration for an actor of the specified type /// - /// - /// Props configuration instance + /// The type of actor the configuration is based + /// The configuration object for the given actor type Props Create() where TActor : ActorBase; /// - /// This method is used to signal the DI Container that it can - /// release it's reference to the actor. HERE + /// Signals the DI container to release it's reference to the actor. + /// HERE /// - /// + /// The actor to remove from the container void Release(ActorBase actor); } } diff --git a/src/contrib/dependencyInjection/Akka.DI.Core/Readme.md b/src/contrib/dependencyInjection/Akka.DI.Core/Readme.md index c5b54abba34..dc2d78c564d 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Core/Readme.md +++ b/src/contrib/dependencyInjection/Akka.DI.Core/Readme.md @@ -21,7 +21,7 @@ Let's walk through the process of creating one for CastleWindsor container. You throw new NotImplementedException(); } - Func CreateActorFactory(string ActorName) + Func CreateActorFactory(Type actorType) { throw new NotImplementedException(); } @@ -78,9 +78,9 @@ First you need to implement GetType. This is a basic implementation and is just Secondly you need to implement the CreateActorFactory method which will be used by the extension to create the Actor. This implementation will depend upon the API of the container. - public Func CreateActorFactory(string actorName) + public Func CreateActorFactory(Type actorType) { - return () => (ActorBase)container.Resolve(GetType(actorName)); + return () => (ActorBase)container.Resolve(actorType); } Thirdly, you implement the Create which is used register the Props configuration for the referenced Actor Type with the ActorSystem. This method will always be the same implementation. @@ -122,4 +122,10 @@ So with that you can do something like the following code example: Name = Guid.NewGuid().ToString() }; hashGroup.Tell(msg); - } \ No newline at end of file + } + +## Creating Child Actors using DI ## +When you want to create child actors from within your existing actors using the Dependency Injection you can just use the Actor Content extension just like in the following example. + + Context.DI().ActorOf().Tell(message); + diff --git a/src/contrib/dependencyInjection/Akka.DI.Ninject/Akka.DI.Ninject.csproj b/src/contrib/dependencyInjection/Akka.DI.Ninject/Akka.DI.Ninject.csproj index b81e570d523..93dc1bce87b 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Ninject/Akka.DI.Ninject.csproj +++ b/src/contrib/dependencyInjection/Akka.DI.Ninject/Akka.DI.Ninject.csproj @@ -41,11 +41,7 @@ - - - - diff --git a/src/contrib/dependencyInjection/Akka.DI.Ninject/Akka.DI.Ninject.nuspec b/src/contrib/dependencyInjection/Akka.DI.Ninject/Akka.DI.Ninject.nuspec index 7a12dff2fae..bae363a0e66 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Ninject/Akka.DI.Ninject.nuspec +++ b/src/contrib/dependencyInjection/Akka.DI.Ninject/Akka.DI.Ninject.nuspec @@ -9,7 +9,7 @@ Ninject Dependency Injection (DI) support for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/contrib/dependencyInjection/Akka.DI.Ninject/NinjectDependencyResolver.cs b/src/contrib/dependencyInjection/Akka.DI.Ninject/NinjectDependencyResolver.cs index 21437c64554..e0402636ede 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Ninject/NinjectDependencyResolver.cs +++ b/src/contrib/dependencyInjection/Akka.DI.Ninject/NinjectDependencyResolver.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Concurrent; -using System.Linq; using Akka.Actor; using Akka.DI.Core; using Ninject; @@ -15,9 +14,8 @@ namespace Akka.DI.Ninject { /// - /// Provide services to ActorSystem Extension system used to create Actor - /// using the Ninject IOC Container to handle wiring up dependencies to - /// Actors + /// Provides services to the extension system + /// used to create actors using the Ninject IoC container. /// public class NinjectDependencyResolver : IDependencyResolver { @@ -27,10 +25,13 @@ public class NinjectDependencyResolver : IDependencyResolver private ActorSystem system; /// - /// NinjectDependencyResolver Constructor + /// Initializes a new instance of the class. /// - /// Instance IKernel - /// Instance to ActorSystem + /// The container used to resolve references + /// The actor system to plug into + /// + /// Either the or the was null. + /// public NinjectDependencyResolver(IKernel container, ActorSystem system) { if (system == null) throw new ArgumentNullException("system"); @@ -40,69 +41,47 @@ public NinjectDependencyResolver(IKernel container, ActorSystem system) this.system = system; this.system.AddDependencyResolver(this); } + /// - /// Returns the Type for the Actor Type specified in the actorName + /// Retrieves an actor's type with the specified name /// - /// - /// + /// The name of the actor to retrieve + /// The type with the specified actor name public Type GetType(string actorName) { typeCache.TryAdd(actorName, actorName.GetTypeValue()); return typeCache[actorName]; } + /// - /// Creates a delegate factory based on the actorName + /// Creates a delegate factory used to create actors based on their type /// - /// Name of the ActorType - /// factory delegate - public Func CreateActorFactory(string actorName) + /// The type of actor that the factory builds + /// A delegate factory used to create actors + public Func CreateActorFactory(Type actorType) { - return () => - { - Type actorType = this.GetType(actorName); - - return (ActorBase)container.GetService(actorType); - }; + return () => (ActorBase)container.GetService(actorType); } + /// - /// Used Register the Configuration for the ActorType specified in TActor + /// Used to register the configuration for an actor of the specified type /// - /// Tye of Actor that needs to be created - /// Props configuration instance + /// The type of actor the configuration is based + /// The configuration object for the given actor type public Props Create() where TActor : ActorBase { - return system.GetExtension().Props(typeof(TActor).Name); + return system.GetExtension().Props(typeof(TActor)); } /// - /// This method is used to signal the DI Container that it can - /// release it's reference to the actor. HERE + /// Signals the DI container to release it's reference to the actor. + /// HERE /// - /// + /// The actor to remove from the container public void Release(ActorBase actor) { container.Release(actor); } } - internal static class Extensions - { - public static Type GetTypeValue(this string typeName) - { - var firstTry = Type.GetType(typeName); - Func searchForType = () => - { - return - AppDomain. - CurrentDomain. - GetAssemblies(). - SelectMany(x => x.GetTypes()). - Where(t => t.Name.Equals(typeName)). - FirstOrDefault(); - }; - return firstTry ?? searchForType(); - } - - } } - diff --git a/src/contrib/dependencyInjection/Akka.DI.Unity/Akka.DI.Unity.csproj b/src/contrib/dependencyInjection/Akka.DI.Unity/Akka.DI.Unity.csproj index 582ab7f4813..7f802562a1b 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Unity/Akka.DI.Unity.csproj +++ b/src/contrib/dependencyInjection/Akka.DI.Unity/Akka.DI.Unity.csproj @@ -44,11 +44,7 @@ - - - - diff --git a/src/contrib/dependencyInjection/Akka.DI.Unity/Akka.DI.Unity.nuspec b/src/contrib/dependencyInjection/Akka.DI.Unity/Akka.DI.Unity.nuspec index 0f35105216c..b3a61d475ed 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Unity/Akka.DI.Unity.nuspec +++ b/src/contrib/dependencyInjection/Akka.DI.Unity/Akka.DI.Unity.nuspec @@ -9,7 +9,7 @@ Unity Dependency Injection (DI) support for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/contrib/dependencyInjection/Akka.DI.Unity/Properties/AssemblyInfo.cs b/src/contrib/dependencyInjection/Akka.DI.Unity/Properties/AssemblyInfo.cs index c04ebda293b..c03a0b64133 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Unity/Properties/AssemblyInfo.cs +++ b/src/contrib/dependencyInjection/Akka.DI.Unity/Properties/AssemblyInfo.cs @@ -6,7 +6,6 @@ //----------------------------------------------------------------------- using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/contrib/dependencyInjection/Akka.DI.Unity/UnityDependencyResolver.cs b/src/contrib/dependencyInjection/Akka.DI.Unity/UnityDependencyResolver.cs index 4561747231f..17000f2f7f8 100644 --- a/src/contrib/dependencyInjection/Akka.DI.Unity/UnityDependencyResolver.cs +++ b/src/contrib/dependencyInjection/Akka.DI.Unity/UnityDependencyResolver.cs @@ -7,76 +7,80 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Akka.Actor; using Akka.DI.Core; using Microsoft.Practices.Unity; namespace Akka.DI.Unity { + /// + /// Provides services to the extension system + /// used to create actors using the Unity IoC container. + /// public class UnityDependencyResolver : IDependencyResolver { - private IUnityContainer container; - private ConcurrentDictionary typeCache; - private ActorSystem system; + private IUnityContainer container; + private ConcurrentDictionary typeCache; + private ActorSystem system; - public UnityDependencyResolver(IUnityContainer container, ActorSystem system) - { - if (system == null) throw new ArgumentNullException("system"); + /// + /// Initializes a new instance of the class. + /// + /// The container used to resolve references + /// The actor system to plug into + /// + /// Either the or the was null. + /// + public UnityDependencyResolver(IUnityContainer container, ActorSystem system) + { + if (system == null) throw new ArgumentNullException("system"); if (container == null) throw new ArgumentNullException("container"); this.container = container; - typeCache = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - this.system = system; + typeCache = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + this.system = system; this.system.AddDependencyResolver(this); - } + } - public Type GetType(string actorName) - { - typeCache.TryAdd(actorName, actorName.GetTypeValue()); + /// + /// Retrieves an actor's type with the specified name + /// + /// The name of the actor to retrieve + /// The type with the specified actor name + public Type GetType(string actorName) + { + typeCache.TryAdd(actorName, actorName.GetTypeValue()); return typeCache[actorName]; - } - - public Func CreateActorFactory(string actorName) - { - return () => - { - var actorType = GetType(actorName); - return (ActorBase)container.Resolve(actorType); - }; - } + } - public Props Create() where TActor : ActorBase - { - return system.GetExtension().Props(typeof(TActor).Name); - } + /// + /// Creates a delegate factory used to create actors based on their type + /// + /// The type of actor that the factory builds + /// A delegate factory used to create actors + public Func CreateActorFactory(Type actorType) + { + return () => (ActorBase)container.Resolve(actorType); + } - public void Release(ActorBase actor) - { - container.Teardown(actor); - } - } + /// + /// Used to register the configuration for an actor of the specified type + /// + /// The type of actor the configuration is based + /// The configuration object for the given actor type + public Props Create() where TActor : ActorBase + { + return system.GetExtension().Props(typeof(TActor)); + } - internal static class Extensions - { - public static Type GetTypeValue(this string typeName) + /// + /// Signals the DI container to release it's reference to the actor. + /// HERE + /// + /// The actor to remove from the container + public void Release(ActorBase actor) { - var firstTry = Type.GetType(typeName); - Func searchForType = () => - { - return - AppDomain. - CurrentDomain. - GetAssemblies(). - SelectMany(x => x.GetTypes()). - Where(t => t.Name.Equals(typeName)). - FirstOrDefault(); - }; - return firstTry ?? searchForType(); + container.Teardown(actor); } } } - diff --git a/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Actors.cs b/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Actors.cs index 01179ba39af..1c4b50d6b05 100644 --- a/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Actors.cs +++ b/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Actors.cs @@ -69,7 +69,7 @@ public void Handle(TypedActorMessage message) { Console.WriteLine("TypedParentWorker - {0} received {1}", Self.Path.Name, message); var producer = Context.System.GetExtension(); - Context.ActorOf(producer.Props("TypedWorker")).Tell(message); + Context.DI().ActorOf().Tell(message); } diff --git a/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/BasicAutoFacUses.csproj b/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/BasicAutoFacUses.csproj index 1d5e0caf5da..ad04a52d71c 100644 --- a/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/BasicAutoFacUses.csproj +++ b/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/BasicAutoFacUses.csproj @@ -42,11 +42,7 @@ - - - - diff --git a/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Program.cs b/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Program.cs index 56ea77d8f7d..cc51a070b16 100644 --- a/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Program.cs +++ b/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Program.cs @@ -54,10 +54,13 @@ private static void WithHashPool() } } + Console.WriteLine("Hit Enter to close"); + Console.ReadLine(); } - Console.ReadLine(); + } + } } diff --git a/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Properties/AssemblyInfo.cs b/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Properties/AssemblyInfo.cs index 202886427c6..fa058e52df6 100644 --- a/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Properties/AssemblyInfo.cs +++ b/src/contrib/dependencyInjection/Examples/BasicAutoFacUses/Properties/AssemblyInfo.cs @@ -6,7 +6,6 @@ //----------------------------------------------------------------------- using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/BasicCastleWindsorUses.csproj b/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/BasicCastleWindsorUses.csproj index 484c15ba50c..9f45d0cf868 100644 --- a/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/BasicCastleWindsorUses.csproj +++ b/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/BasicCastleWindsorUses.csproj @@ -45,11 +45,7 @@ - - - - diff --git a/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/Program.cs b/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/Program.cs index 5ae92a2f6d3..ba4bb6c2edb 100644 --- a/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/Program.cs +++ b/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/Program.cs @@ -52,9 +52,11 @@ private static void WithHashPool() } } + Console.WriteLine("Hit Enter to exit"); + Console.ReadLine(); } - Console.ReadLine(); + } } } diff --git a/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/Properties/AssemblyInfo.cs b/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/Properties/AssemblyInfo.cs index 7a0317e7094..25dd303d5c4 100644 --- a/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/Properties/AssemblyInfo.cs +++ b/src/contrib/dependencyInjection/Examples/BasicCastleWindsorUse/Properties/AssemblyInfo.cs @@ -6,7 +6,6 @@ //----------------------------------------------------------------------- using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/contrib/dependencyInjection/Examples/BasicNinjectUses/BasicNinjectUses.csproj b/src/contrib/dependencyInjection/Examples/BasicNinjectUses/BasicNinjectUses.csproj index 2afd70fbbba..26b77f30c44 100644 --- a/src/contrib/dependencyInjection/Examples/BasicNinjectUses/BasicNinjectUses.csproj +++ b/src/contrib/dependencyInjection/Examples/BasicNinjectUses/BasicNinjectUses.csproj @@ -42,11 +42,7 @@ - - - - diff --git a/src/contrib/dependencyInjection/Examples/BasicNinjectUses/Program.cs b/src/contrib/dependencyInjection/Examples/BasicNinjectUses/Program.cs index 7c1ec70c45b..0d045e10423 100644 --- a/src/contrib/dependencyInjection/Examples/BasicNinjectUses/Program.cs +++ b/src/contrib/dependencyInjection/Examples/BasicNinjectUses/Program.cs @@ -51,10 +51,11 @@ private static void WithHashPool() } } + Console.WriteLine("Hit Enter to exit"); + Console.ReadLine(); } - Console.ReadLine(); } } } diff --git a/src/contrib/dependencyInjection/Examples/BasicNinjectUses/Properties/AssemblyInfo.cs b/src/contrib/dependencyInjection/Examples/BasicNinjectUses/Properties/AssemblyInfo.cs index b2314c8a436..6308719ac08 100644 --- a/src/contrib/dependencyInjection/Examples/BasicNinjectUses/Properties/AssemblyInfo.cs +++ b/src/contrib/dependencyInjection/Examples/BasicNinjectUses/Properties/AssemblyInfo.cs @@ -6,7 +6,6 @@ //----------------------------------------------------------------------- using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/contrib/dependencyInjection/Examples/BasicUnityUses/BasicUnityUses.csproj b/src/contrib/dependencyInjection/Examples/BasicUnityUses/BasicUnityUses.csproj index da656171ca9..4456bdce213 100644 --- a/src/contrib/dependencyInjection/Examples/BasicUnityUses/BasicUnityUses.csproj +++ b/src/contrib/dependencyInjection/Examples/BasicUnityUses/BasicUnityUses.csproj @@ -51,11 +51,7 @@ - - - - diff --git a/src/contrib/dependencyInjection/Examples/BasicUnityUses/Program.cs b/src/contrib/dependencyInjection/Examples/BasicUnityUses/Program.cs index a54124e8151..0281c33a329 100644 --- a/src/contrib/dependencyInjection/Examples/BasicUnityUses/Program.cs +++ b/src/contrib/dependencyInjection/Examples/BasicUnityUses/Program.cs @@ -6,9 +6,6 @@ //----------------------------------------------------------------------- using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Akka.Actor; using Akka.DI.Unity; @@ -17,14 +14,14 @@ namespace BasicUnityUses { - class Program - { - static void Main(string[] args) - { - WithHashPool(); - } + class Program + { + static void Main(string[] args) + { + WithHashPool(); + } - private static void WithHashPool() + private static void WithHashPool() { IUnityContainer container = new UnityContainer(); container.RegisterType(); @@ -55,11 +52,12 @@ private static void WithHashPool() } } + Console.WriteLine("Hit Enter to exit"); + Console.ReadLine(); } - Console.ReadLine(); } - } + } } diff --git a/src/contrib/dependencyInjection/Examples/BasicUnityUses/Properties/AssemblyInfo.cs b/src/contrib/dependencyInjection/Examples/BasicUnityUses/Properties/AssemblyInfo.cs index a8f37536a48..f8021ace6cd 100644 --- a/src/contrib/dependencyInjection/Examples/BasicUnityUses/Properties/AssemblyInfo.cs +++ b/src/contrib/dependencyInjection/Examples/BasicUnityUses/Properties/AssemblyInfo.cs @@ -6,7 +6,6 @@ //----------------------------------------------------------------------- using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/contrib/loggers/Akka.Logger.NLog/Akka.Logger.NLog.csproj b/src/contrib/loggers/Akka.Logger.NLog/Akka.Logger.NLog.csproj index 0f150bc4b3d..7b08686e5e5 100644 --- a/src/contrib/loggers/Akka.Logger.NLog/Akka.Logger.NLog.csproj +++ b/src/contrib/loggers/Akka.Logger.NLog/Akka.Logger.NLog.csproj @@ -38,11 +38,7 @@ - - - - diff --git a/src/contrib/loggers/Akka.Logger.NLog/Akka.Logger.NLog.nuspec b/src/contrib/loggers/Akka.Logger.NLog/Akka.Logger.NLog.nuspec index c6b2cfa5af4..61b27321a3f 100644 --- a/src/contrib/loggers/Akka.Logger.NLog/Akka.Logger.NLog.nuspec +++ b/src/contrib/loggers/Akka.Logger.NLog/Akka.Logger.NLog.nuspec @@ -9,7 +9,7 @@ NLog logging adapter for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/contrib/loggers/Akka.Logger.Serilog/Akka.Logger.Serilog.csproj b/src/contrib/loggers/Akka.Logger.Serilog/Akka.Logger.Serilog.csproj index f02f2d00f3c..4798be337d2 100644 --- a/src/contrib/loggers/Akka.Logger.Serilog/Akka.Logger.Serilog.csproj +++ b/src/contrib/loggers/Akka.Logger.Serilog/Akka.Logger.Serilog.csproj @@ -41,11 +41,7 @@ - - - - diff --git a/src/contrib/loggers/Akka.Logger.Serilog/Akka.Logger.Serilog.nuspec b/src/contrib/loggers/Akka.Logger.Serilog/Akka.Logger.Serilog.nuspec index 1caf3c17194..d8785a5d894 100644 --- a/src/contrib/loggers/Akka.Logger.Serilog/Akka.Logger.Serilog.nuspec +++ b/src/contrib/loggers/Akka.Logger.Serilog/Akka.Logger.Serilog.nuspec @@ -9,7 +9,7 @@ Serilog logging adapter for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/contrib/loggers/Akka.Logger.slf4net/Akka.Logger.slf4net.csproj b/src/contrib/loggers/Akka.Logger.slf4net/Akka.Logger.slf4net.csproj index 8b92b6e3edb..8c913478b38 100644 --- a/src/contrib/loggers/Akka.Logger.slf4net/Akka.Logger.slf4net.csproj +++ b/src/contrib/loggers/Akka.Logger.slf4net/Akka.Logger.slf4net.csproj @@ -59,11 +59,7 @@ - - - - diff --git a/src/contrib/loggers/Akka.Logger.slf4net/Akka.Logger.slf4net.nuspec b/src/contrib/loggers/Akka.Logger.slf4net/Akka.Logger.slf4net.nuspec index 13ef38fe3fb..22501d492e2 100644 --- a/src/contrib/loggers/Akka.Logger.slf4net/Akka.Logger.slf4net.nuspec +++ b/src/contrib/loggers/Akka.Logger.slf4net/Akka.Logger.slf4net.nuspec @@ -9,7 +9,7 @@ slf4net logging adapter for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Akka.Persistence.SqlServer.Tests.csproj b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Akka.Persistence.SqlServer.Tests.csproj new file mode 100644 index 00000000000..5c19ca2eaaa --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Akka.Persistence.SqlServer.Tests.csproj @@ -0,0 +1,135 @@ + + + + + Debug + AnyCPU + {5A3C24D7-0D1C-4974-BBB4-22AC792666DE} + Library + Properties + Akka.Persistence.SqlServer.Tests + Akka.Persistence.SqlServer.Tests + v4.5 + 512 + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + True + True + Settings.settings + + + + + + + {ad9418b6-c452-4169-94fb-d43de0bfa966} + Akka.Persistence.TestKit + + + {fca84dea-c118-424b-9eb8-34375dfef18a} + Akka.Persistence + + + {0d3cbad0-bbdb-43e5-afc4-ed1d3ecdc224} + Akka.TestKit + + + {5deddf90-37f0-48d3-a0b0-a5cbd8a7e377} + Akka + + + {11f4d4b8-7e07-4457-abf2-609b3e7b2649} + Akka.TestKit.Xunit + + + {bac85686-afc4-413e-98dc-5ed8f468bc63} + Akka.Persistence.SqlServer + + + + + False + Microsoft .NET Framework 4.5 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + Always + + + Always + AkkaPersistenceSqlServerSpecDb.mdf + + + + + \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/DbCleanup.cs b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/DbCleanup.cs new file mode 100644 index 00000000000..3cad829bf07 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/DbCleanup.cs @@ -0,0 +1,29 @@ +using System; +using System.Data.SqlClient; + +namespace Akka.Persistence.SqlServer.Tests +{ + public static class DbCleanup + { + private static readonly string ConnectionString = @"Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\Resources\AkkaPersistenceSqlServerSpecDb.mdf;Integrated Security=True"; + + static DbCleanup() + { + AppDomain.CurrentDomain.SetData("DataDirectory", AppDomain.CurrentDomain.BaseDirectory); + } + + public static void Clean() + { + using (var conn = new SqlConnection(ConnectionString)) + using (var cmd = new SqlCommand()) + { + conn.Open(); + cmd.CommandText = @" + IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'EventJournal') BEGIN DELETE FROM dbo.EventJournal END; + IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'SnapshotStore') BEGIN DELETE FROM dbo.SnapshotStore END"; + cmd.Connection = conn; + cmd.ExecuteNonQuery(); + } + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Properties/AssemblyInfo.cs b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..c1fbc29c2eb --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Akka.Persistence.SqlServer.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Akka.Persistence.SqlServer.Tests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("343a20b5-0aa8-4c22-baea-de78162b1c94")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Properties/Settings.Designer.cs b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Properties/Settings.Designer.cs new file mode 100644 index 00000000000..1c96125f53e --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Properties/Settings.Designer.cs @@ -0,0 +1,37 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.35312 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Akka.Persistence.SqlServer.Tests.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.SpecialSettingAttribute(global::System.Configuration.SpecialSetting.ConnectionString)] + [global::System.Configuration.DefaultSettingValueAttribute("Data Source=(LocalDB)\\v11.0;AttachDbFilename=|DataDirectory|\\SqlServerJournalSpec" + + "Db.mdf;Integrated Security=True;Connect Timeout=30")] + public string SqlServerJournalSpecDbConnectionString { + get { + return ((string)(this["SqlServerJournalSpecDbConnectionString"])); + } + } + } +} diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Properties/Settings.settings b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Properties/Settings.settings new file mode 100644 index 00000000000..bc01a8a91e9 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Properties/Settings.settings @@ -0,0 +1,14 @@ + + + + + + <?xml version="1.0" encoding="utf-16"?> +<SerializableConnectionString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <ConnectionString>Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\SqlServerJournalSpecDb.mdf;Integrated Security=True;Connect Timeout=30</ConnectionString> + <ProviderName>System.Data.SqlClient</ProviderName> +</SerializableConnectionString> + Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\SqlServerJournalSpecDb.mdf;Integrated Security=True;Connect Timeout=30 + + + \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Resources/AkkaPersistenceSqlServerSpecDb.mdf b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Resources/AkkaPersistenceSqlServerSpecDb.mdf new file mode 100644 index 00000000000..eac1320465c Binary files /dev/null and b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Resources/AkkaPersistenceSqlServerSpecDb.mdf differ diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Resources/AkkaPersistenceSqlServerSpecDb_log.ldf b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Resources/AkkaPersistenceSqlServerSpecDb_log.ldf new file mode 100644 index 00000000000..71c8b5ba355 Binary files /dev/null and b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/Resources/AkkaPersistenceSqlServerSpecDb_log.ldf differ diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/SqlServerJournalSpec.cs b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/SqlServerJournalSpec.cs new file mode 100644 index 00000000000..f2d423d4b36 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/SqlServerJournalSpec.cs @@ -0,0 +1,37 @@ +using Akka.Configuration; +using Akka.Persistence.TestKit.Journal; + +namespace Akka.Persistence.SqlServer.Tests +{ + public class SqlServerJournalSpec : JournalSpec + { + private static readonly Config SpecConfig = ConfigurationFactory.ParseString(@" + akka.persistence { + publish-plugin-commands = on + journal { + plugin = ""akka.persistence.journal.sql-server"" + sql-server { + class = ""Akka.Persistence.SqlServer.Journal.SqlServerJournal, Akka.Persistence.SqlServer"" + plugin-dispatcher = ""akka.actor.default-dispatcher"" + table-name = EventJournal + schema-name = dbo + auto-initialize = on + connection-string = ""Data Source=(LocalDB)\\v11.0;AttachDbFilename=|DataDirectory|\\Resources\\AkkaPersistenceSqlServerSpecDb.mdf;Integrated Security=True"" + } + } + }"); + + public SqlServerJournalSpec() + : base(SpecConfig, "SqlServerJournalSpec") + { + DbCleanup.Clean(); + Initialize(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + DbCleanup.Clean(); + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/SqlServerSnapshotStoreSpec.cs b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/SqlServerSnapshotStoreSpec.cs new file mode 100644 index 00000000000..6326171f553 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/SqlServerSnapshotStoreSpec.cs @@ -0,0 +1,37 @@ +using Akka.Configuration; +using Akka.Persistence.TestKit.Snapshot; + +namespace Akka.Persistence.SqlServer.Tests +{ + public class SqlServerSnapshotStoreSpec : SnapshotStoreSpec + { + private static readonly Config SpecConfig = ConfigurationFactory.ParseString(@" + akka.persistence { + publish-plugin-commands = on + snapshot-store { + plugin = ""akka.persistence.snapshot-store.sql-server"" + sql-server { + class = ""Akka.Persistence.SqlServer.Snapshot.SqlServerSnapshotStore, Akka.Persistence.SqlServer"" + plugin-dispatcher = ""akka.actor.default-dispatcher"" + table-name = SnapshotStore + schema-name = dbo + auto-initialize = on + connection-string = ""Data Source=(LocalDB)\\v11.0;AttachDbFilename=|DataDirectory|\\Resources\\AkkaPersistenceSqlServerSpecDb.mdf;Integrated Security=True"" + } + } + }"); + + public SqlServerSnapshotStoreSpec() + : base(SpecConfig, "SqlServerSnapshotStoreSpec") + { + DbCleanup.Clean(); + Initialize(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + DbCleanup.Clean(); + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/app.config b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/app.config new file mode 100644 index 00000000000..49cc43e1d8f --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer.Tests/app.config @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/Akka.Persistence.SqlServer.csproj b/src/contrib/persistence/Akka.Persistence.SqlServer/Akka.Persistence.SqlServer.csproj new file mode 100644 index 00000000000..4847156456b --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/Akka.Persistence.SqlServer.csproj @@ -0,0 +1,82 @@ + + + + + Debug + AnyCPU + {BAC85686-AFC4-413E-98DC-5ED8F468BC63} + Library + Properties + Akka.Persistence.SqlServer + Akka.Persistence.SqlServer + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + {fca84dea-c118-424b-9eb8-34375dfef18a} + Akka.Persistence + + + {5deddf90-37f0-48d3-a0b0-a5cbd8a7e377} + Akka + + + + + Always + + + + + + + + + + + \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/Akka.Persistence.SqlServer.nuspec b/src/contrib/persistence/Akka.Persistence.SqlServer/Akka.Persistence.SqlServer.nuspec new file mode 100644 index 00000000000..bcd3f99250e --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/Akka.Persistence.SqlServer.nuspec @@ -0,0 +1,20 @@ + + + + @project@ + @project@@title@ + @build.number@ + @authors@ + @authors@ + Akka.NET Persistence journal and snapshot store backed by SQL Server. + https://github.com/akkadotnet/akka.net/blob/master/LICENSE + https://github.com/akkadotnet/akka.net + http://getakka.net/images/AkkaNetLogo.Normal.png + false + @releaseNotes@ + @copyright@ + @tags@ persistence eventsource sqlserver + @dependencies@ + @references@ + + diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/Extension.cs b/src/contrib/persistence/Akka.Persistence.SqlServer/Extension.cs new file mode 100644 index 00000000000..df961f40972 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/Extension.cs @@ -0,0 +1,165 @@ +using System; +using Akka.Actor; +using Akka.Configuration; + +namespace Akka.Persistence.SqlServer +{ + /// + /// Configuration settings representation targeting Sql Server journal actor. + /// + public class JournalSettings + { + public const string ConfigPath = "akka.persistence.journal.sql-server"; + + /// + /// Connection string used to access a persistent SQL Server instance. + /// + public string ConnectionString { get; private set; } + + /// + /// Connection timeout for SQL Server related operations. + /// + public TimeSpan ConnectionTimeout { get; private set; } + + /// + /// Schema name, where table corresponding to event journal is placed. + /// + public string SchemaName { get; private set; } + + /// + /// Name of the table corresponding to event journal. + /// + public string TableName { get; private set; } + + /// + /// Flag determining in in case of event journal table missing, it should be automatically initialized. + /// + public bool AutoInitialize { get; private set; } + + public JournalSettings(Config config) + { + if (config == null) throw new ArgumentNullException("config", "SqlServer journal settings cannot be initialized, because required HOCON section couldn't been found"); + + ConnectionString = config.GetString("connection-string"); + ConnectionTimeout = config.GetTimeSpan("connection-timeout"); + SchemaName = config.GetString("schema-name"); + TableName = config.GetString("table-name"); + AutoInitialize = config.GetBoolean("auto-initialize"); + } + } + + /// + /// Configuration settings representation targeting Sql Server snapshot store actor. + /// + public class SnapshotStoreSettings + { + public const string ConfigPath = "akka.persistence.snapshot-store.sql-server"; + + /// + /// Connection string used to access a persistent SQL Server instance. + /// + public string ConnectionString { get; private set; } + + /// + /// Connection timeout for SQL Server related operations. + /// + public TimeSpan ConnectionTimeout { get; private set; } + + /// + /// Schema name, where table corresponding to snapshot store is placed. + /// + public string SchemaName { get; private set; } + + /// + /// Name of the table corresponding to snapshot store. + /// + public string TableName { get; private set; } + + /// + /// Flag determining in in case of snapshot store table missing, it should be automatically initialized. + /// + public bool AutoInitialize { get; private set; } + + public SnapshotStoreSettings(Config config) + { + if (config == null) throw new ArgumentNullException("config", "SqlServer snapshot store settings cannot be initialized, because required HOCON section couldn't been found"); + + ConnectionString = config.GetString("connection-string"); + ConnectionTimeout = config.GetTimeSpan("connection-timeout"); + SchemaName = config.GetString("schema-name"); + TableName = config.GetString("table-name"); + AutoInitialize = config.GetBoolean("auto-initialize"); + } + } + + /// + /// An actor system extension initializing support for SQL Server persistence layer. + /// + public class SqlServerPersistenceExtension : IExtension + { + /// + /// Journal-related settings loaded from HOCON configuration. + /// + public readonly JournalSettings JournalSettings; + + /// + /// Snapshot store related settings loaded from HOCON configuration. + /// + public readonly SnapshotStoreSettings SnapshotStoreSettings; + + public SqlServerPersistenceExtension(ExtendedActorSystem system) + { + system.Settings.InjectTopLevelFallback(SqlServerPersistence.DefaultConfiguration()); + + JournalSettings = new JournalSettings(system.Settings.Config.GetConfig(JournalSettings.ConfigPath)); + SnapshotStoreSettings = new SnapshotStoreSettings(system.Settings.Config.GetConfig(SnapshotStoreSettings.ConfigPath)); + + if (JournalSettings.AutoInitialize) + { + SqlServerInitializer.CreateSqlServerJournalTables(JournalSettings.ConnectionString, JournalSettings.SchemaName, JournalSettings.TableName); + } + + if (SnapshotStoreSettings.AutoInitialize) + { + SqlServerInitializer.CreateSqlServerSnapshotStoreTables(SnapshotStoreSettings.ConnectionString, SnapshotStoreSettings.SchemaName, SnapshotStoreSettings.TableName); + } + } + } + + /// + /// Singleton class used to setup SQL Server backend for akka persistence plugin. + /// + public class SqlServerPersistence : ExtensionIdProvider + { + public static readonly SqlServerPersistence Instance = new SqlServerPersistence(); + + /// + /// Initializes a SQL Server persistence plugin inside provided . + /// + public static void Init(ActorSystem actorSystem) + { + Instance.Apply(actorSystem); + } + + private SqlServerPersistence() { } + + /// + /// Creates an actor system extension for akka persistence SQL Server support. + /// + /// + /// + public override SqlServerPersistenceExtension CreateExtension(ExtendedActorSystem system) + { + return new SqlServerPersistenceExtension(system); + } + + /// + /// Returns a default configuration for akka persistence SQL Server-based journals and snapshot stores. + /// + /// + public static Config DefaultConfiguration() + { + return ConfigurationFactory.FromResource("Akka.Persistence.SqlServer.sql-server.conf"); + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/InternalExtensions.cs b/src/contrib/persistence/Akka.Persistence.SqlServer/InternalExtensions.cs new file mode 100644 index 00000000000..1fc0293dbc1 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/InternalExtensions.cs @@ -0,0 +1,19 @@ +using System; +using System.Data.SqlClient; + +namespace Akka.Persistence.SqlServer +{ + internal static class InternalExtensions + { + public static string QualifiedTypeName(this Type type) + { + return type.FullName + ", " + type.Assembly.GetName().Name; + } + + public static string QuoteSchemaAndTable(this string sqlQuery, string schemaName, string tableName) + { + var cb = new SqlCommandBuilder(); + return string.Format(sqlQuery, cb.QuoteIdentifier(schemaName), cb.QuoteIdentifier(tableName)); + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/Journal/QueryBuilder.cs b/src/contrib/persistence/Akka.Persistence.SqlServer/Journal/QueryBuilder.cs new file mode 100644 index 00000000000..9a4559e23f8 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/Journal/QueryBuilder.cs @@ -0,0 +1,158 @@ +using System.Data; +using System.Data.SqlClient; +using System.Text; + +namespace Akka.Persistence.SqlServer.Journal +{ + /// + /// SQL query builder used for generating queries required to perform journal's tasks. + /// + public interface IJournalQueryBuilder + { + /// + /// Returns query which should return a frame of messages filtered accordingly to provided parameters. + /// + SqlCommand SelectMessages(string persistenceId, long fromSequenceNr, long toSequenceNr, long max); + + /// + /// Returns query returning single number considered as the highest sequence number in current journal. + /// + SqlCommand SelectHighestSequenceNr(string persistenceId); + + /// + /// Returns a non-query command used to insert collection of in journal table. + /// + SqlCommand InsertBatchMessages(IPersistentRepresentation[] messages); + + /// + /// Depending on flag this method may return either UPDATE or DELETE statement + /// used to alter IsDeleted field or delete rows permanently. + /// + SqlCommand DeleteBatchMessages(string persistenceId, long toSequenceNr, bool permanent); + } + + internal class DefaultJournalQueryBuilder : IJournalQueryBuilder + { + private readonly string _schemaName; + private readonly string _tableName; + + private readonly string _selectHighestSequenceNrSql; + private readonly string _insertMessagesSql; + + public DefaultJournalQueryBuilder(string tableName, string schemaName) + { + _tableName = tableName; + _schemaName = schemaName; + + _insertMessagesSql = "INSERT INTO {0}.{1} (PersistenceID, SequenceNr, IsDeleted, PayloadType, Payload) VALUES (@PersistenceId, @SequenceNr, @IsDeleted, @PayloadType, @Payload)" + .QuoteSchemaAndTable(_schemaName, _tableName); + _selectHighestSequenceNrSql = @"SELECT MAX(SequenceNr) FROM {0}.{1} WHERE CS_PID = CHECKSUM(@pid)".QuoteSchemaAndTable(_schemaName, _tableName); + } + + public SqlCommand SelectMessages(string persistenceId, long fromSequenceNr, long toSequenceNr, long max) + { + var sql = BuildSelectMessagesSql(fromSequenceNr, toSequenceNr, max); + var command = new SqlCommand(sql) + { + Parameters = { PersistenceIdToSqlParam(persistenceId) } + }; + + return command; + } + + public SqlCommand SelectHighestSequenceNr(string persistenceId) + { + var command = new SqlCommand(_selectHighestSequenceNrSql) + { + Parameters = { PersistenceIdToSqlParam(persistenceId) } + }; + + return command; + } + + public SqlCommand InsertBatchMessages(IPersistentRepresentation[] messages) + { + var command = new SqlCommand(_insertMessagesSql); + command.Parameters.Add("@PersistenceId", SqlDbType.NVarChar); + command.Parameters.Add("@SequenceNr", SqlDbType.BigInt); + command.Parameters.Add("@IsDeleted", SqlDbType.Bit); + command.Parameters.Add("@PayloadType", SqlDbType.NVarChar); + command.Parameters.Add("@Payload", SqlDbType.VarBinary); + + return command; + } + + public SqlCommand DeleteBatchMessages(string persistenceId, long toSequenceNr, bool permanent) + { + var sql = BuildDeleteSql(toSequenceNr, permanent); + var command = new SqlCommand(sql) + { + Parameters = { PersistenceIdToSqlParam(persistenceId) } + }; + + return command; + } + + private string BuildDeleteSql(long toSequenceNr, bool permanent) + { + var sqlBuilder = new StringBuilder(); + + if (permanent) + { + sqlBuilder.Append("DELETE FROM {0}.{1} ".QuoteSchemaAndTable(_schemaName, _tableName)); + } + else + { + sqlBuilder.Append("UPDATE {0}.{1} SET IsDeleted = 1 ".QuoteSchemaAndTable(_schemaName, _tableName)); + } + + sqlBuilder.Append("WHERE CS_PID = CHECKSUM(@pid)"); + + if (toSequenceNr != long.MaxValue) + { + sqlBuilder.Append(" AND SequenceNr <= ").Append(toSequenceNr); + } + + var sql = sqlBuilder.ToString(); + return sql; + } + + private string BuildSelectMessagesSql(long fromSequenceNr, long toSequenceNr, long max) + { + var sqlBuilder = new StringBuilder(); + sqlBuilder.AppendFormat( + @"SELECT {0} + PersistenceID, + SequenceNr, + IsDeleted, + PayloadType, + Payload ", max != long.MaxValue ? "TOP " + max : string.Empty) + .Append(" FROM {0}.{1} WHERE CS_PID = CHECKSUM(@pid)".QuoteSchemaAndTable(_schemaName, _tableName)); + + // since we guarantee type of fromSequenceNr, toSequenceNr and max + // we can inline them without risk of SQL injection + + if (fromSequenceNr > 0) + { + if (toSequenceNr != long.MaxValue) + sqlBuilder.Append(" AND SequenceNr BETWEEN ") + .Append(fromSequenceNr) + .Append(" AND ") + .Append(toSequenceNr); + else + sqlBuilder.Append(" AND SequenceNr >= ").Append(fromSequenceNr); + } + + if (toSequenceNr != long.MaxValue) + sqlBuilder.Append(" AND SequenceNr <= ").Append(toSequenceNr); + + var sql = sqlBuilder.ToString(); + return sql; + } + + private static SqlParameter PersistenceIdToSqlParam(string persistenceId, string paramName = null) + { + return new SqlParameter(paramName ?? "@pid", SqlDbType.NVarChar, persistenceId.Length) { Value = persistenceId }; + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/Journal/QueryMapper.cs b/src/contrib/persistence/Akka.Persistence.SqlServer/Journal/QueryMapper.cs new file mode 100644 index 00000000000..fa36665fb38 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/Journal/QueryMapper.cs @@ -0,0 +1,47 @@ +using System; +using System.Data.SqlClient; + +namespace Akka.Persistence.SqlServer.Journal +{ + /// + /// Mapper used for generating persistent representations based on SQL query results. + /// + public interface IJournalQueryMapper + { + /// + /// Takes a current row from the SQL data reader and produces a persistent representation object in result. + /// It's not supposed to move reader's cursor in any way. + /// + IPersistentRepresentation Map(SqlDataReader reader); + } + + internal class DefaultJournalQueryMapper : IJournalQueryMapper + { + private readonly Akka.Serialization.Serialization _serialization; + + public DefaultJournalQueryMapper(Akka.Serialization.Serialization serialization) + { + _serialization = serialization; + } + + public IPersistentRepresentation Map(SqlDataReader reader) + { + var persistenceId = reader.GetString(0); + var sequenceNr = reader.GetInt64(1); + var isDeleted = reader.GetBoolean(2); + var payload = GetPayload(reader); + + return new Persistent(payload, sequenceNr, persistenceId, isDeleted); + } + + private object GetPayload(SqlDataReader reader) + { + var payloadType = reader.GetString(3); + var type = Type.GetType(payloadType, true); + var binary = (byte[]) reader[4]; + + var serializer = _serialization.FindSerializerForType(type); + return serializer.FromBinary(binary, type); + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/Journal/SqlServerJournal.cs b/src/contrib/persistence/Akka.Persistence.SqlServer/Journal/SqlServerJournal.cs new file mode 100644 index 00000000000..ade46f0f734 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/Journal/SqlServerJournal.cs @@ -0,0 +1,232 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Akka.Persistence.Journal; + +namespace Akka.Persistence.SqlServer.Journal +{ + /// + /// Persistent journal actor using SQL Server as persistence layer. It processes write requests + /// one by one in synchronous manner, while reading results asynchronously. + /// + public class SqlServerJournal : SyncWriteJournal + { + #region journal internal types definitions + + internal class JournalEntry + { + public readonly string PersistenceId; + public readonly long SequenceNr; + public readonly bool IsDeleted; + public readonly string PayloadType; + public readonly byte[] Payload; + + public JournalEntry(string persistenceId, long sequenceNr, bool isDeleted, string payloadType, byte[] payload) + { + PersistenceId = persistenceId; + SequenceNr = sequenceNr; + IsDeleted = isDeleted; + PayloadType = payloadType; + Payload = payload; + } + } + + #endregion + + private readonly SqlServerPersistenceExtension _extension; + private SqlConnection _connection; + + protected readonly LinkedList PendingOperations; + + /// + /// Used for generating SQL commands for journal-related database operations. + /// + public IJournalQueryBuilder QueryBuilder { get; protected set; } + + /// + /// Used for mapping results returned from database into objects. + /// + public IJournalQueryMapper QueryMapper { get; protected set; } + + public SqlServerJournal() + { + _extension = SqlServerPersistence.Instance.Apply(Context.System); + + var settings = _extension.JournalSettings; + QueryBuilder = new DefaultJournalQueryBuilder(settings.TableName, settings.SchemaName); + QueryMapper = new DefaultJournalQueryMapper(Context.System.Serialization); + PendingOperations = new LinkedList(); + } + + protected override void PreStart() + { + base.PreStart(); + + _connection = new SqlConnection(_extension.JournalSettings.ConnectionString); + _connection.Open(); + } + + protected override void PostStop() + { + base.PostStop(); + + // stop all operations executed in the background + var node = PendingOperations.First; + while (node != null) + { + var curr = node; + node = node.Next; + + curr.Value.Cancel(); + PendingOperations.Remove(curr); + } + + _connection.Close(); + } + + /// + /// Asynchronously replays all requested messages related to provided , + /// using provided sequence ranges (inclusive) with number of messages replayed + /// (counting from the beginning). Replay callback is invoked for each replayed message. + /// + /// Identifier of persistent messages stream to be replayed. + /// Lower inclusive sequence number bound. Unbound by default. + /// Upper inclusive sequence number bound. Unbound by default. + /// Maximum number of messages to be replayed. Unbound by default. + /// Action invoked for each replayed message. + public override Task ReplayMessagesAsync(string persistenceId, long fromSequenceNr, long toSequenceNr, long max, Action replayCallback) + { + var sqlCommand = QueryBuilder.SelectMessages(persistenceId, fromSequenceNr, toSequenceNr, max); + CompleteCommand(sqlCommand); + + var tokenSource = GetCancellationTokenSource(); + + return sqlCommand + .ExecuteReaderAsync(tokenSource.Token) + .ContinueWith(task => + { + var reader = task.Result; + try + { + while (reader.Read()) + { + var persistent = QueryMapper.Map(reader); + if (persistent != null) + replayCallback(persistent); + } + } + finally + { + PendingOperations.Remove(tokenSource); + reader.Close(); + } + }, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.AttachedToParent); + } + + /// + /// Asynchronously reads a highest sequence number of the event stream related with provided . + /// + public override Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr) + { + var sqlCommand = QueryBuilder.SelectHighestSequenceNr(persistenceId); + CompleteCommand(sqlCommand); + + var tokenSource = GetCancellationTokenSource(); + + return sqlCommand + .ExecuteScalarAsync(tokenSource.Token) + .ContinueWith(task => + { + PendingOperations.Remove(tokenSource); + var result = task.Result; + return result is long ? Convert.ToInt64(task.Result) : 0L; + }, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.AttachedToParent); + } + + /// + /// Synchronously writes all persistent inside SQL Server database. + /// + /// Specific table used for message persistence may be defined through configuration within + /// 'akka.persistence.journal.sql-server' scope with 'schema-name' and 'table-name' keys. + /// + public override void WriteMessages(IEnumerable messages) + { + var persistentMessages = messages.ToArray(); + var sqlCommand = QueryBuilder.InsertBatchMessages(persistentMessages); + CompleteCommand(sqlCommand); + + var journalEntires = persistentMessages.Select(ToJournalEntry).ToList(); + + InsertInTransaction(sqlCommand, journalEntires); + } + + /// + /// Synchronously deletes all persisted messages identified by provided + /// up to provided message sequence number (inclusive). If flag is cleared, + /// messages will still reside inside database, but will be logically counted as deleted. + /// + public override void DeleteMessagesTo(string persistenceId, long toSequenceNr, bool isPermanent) + { + var sqlCommand = QueryBuilder.DeleteBatchMessages(persistenceId, toSequenceNr, isPermanent); + CompleteCommand(sqlCommand); + + sqlCommand.ExecuteNonQuery(); + } + + private void CompleteCommand(SqlCommand sqlCommand) + { + sqlCommand.Connection = _connection; + sqlCommand.CommandTimeout = (int)_extension.JournalSettings.ConnectionTimeout.TotalMilliseconds; + } + + private CancellationTokenSource GetCancellationTokenSource() + { + var source = new CancellationTokenSource(); + PendingOperations.AddLast(source); + return source; + } + + private static JournalEntry ToJournalEntry(IPersistentRepresentation message) + { + var payloadType = message.Payload.GetType(); + var serializer = Context.System.Serialization.FindSerializerForType(payloadType); + + return new JournalEntry(message.PersistenceId, message.SequenceNr, message.IsDeleted, + payloadType.QualifiedTypeName(), serializer.ToBinary(message.Payload)); + } + + private void InsertInTransaction(SqlCommand sqlCommand, IEnumerable journalEntires) + { + using (var tx = _connection.BeginTransaction()) + { + sqlCommand.Transaction = tx; + try + { + foreach (var entry in journalEntires) + { + sqlCommand.Parameters["@PersistenceId"].Value = entry.PersistenceId; + sqlCommand.Parameters["@SequenceNr"].Value = entry.SequenceNr; + sqlCommand.Parameters["@IsDeleted"].Value = entry.IsDeleted; + sqlCommand.Parameters["@PayloadType"].Value = entry.PayloadType; + sqlCommand.Parameters["@Payload"].Value = entry.Payload; + + if (sqlCommand.ExecuteNonQuery() != 1) + { + //TODO: something went wrong, ExecuteNonQuery() should return 1 (number of rows added) + } + } + + tx.Commit(); + } + catch (Exception) + { + tx.Rollback(); + throw; + } + } + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/Properties/AssemblyInfo.cs b/src/contrib/persistence/Akka.Persistence.SqlServer/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..b17f32f19d5 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Akka.Persistence.SqlServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Akka.Persistence.SqlServer")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("62ea3143-9e7f-49e3-8d87-44f6b03f31cd")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/README.md b/src/contrib/persistence/Akka.Persistence.SqlServer/README.md new file mode 100644 index 00000000000..48f9225a516 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/README.md @@ -0,0 +1,76 @@ +## Akka.Persistence.SqlServer + +Akka Persistence journal and snapshot store backed by SQL Server database. + +**WARNING: Akka.Persistence.SqlServer plugin is still in beta and it's mechanics described bellow may be still subject to change**. + +### Setup + +To activate the journal plugin, add the following lines to actor system configuration file: + +``` +akka.persistence.journal.plugin = "akka.persistence.journal.sql-server" +akka.persistence.journal.sql-server.connection-string = "" +``` + +Similar configuration may be used to setup a SQL Server snapshot store: + +``` +akka.persistence.snasphot-store.plugin = "akka.persistence.snasphot-store.sql-server" +akka.persistence.snasphot-store.sql-server.connection-string = "" +``` + +Remember that connection string must be provided separately to Journal and Snapshot Store. To finish setup simply initialize plugin using: `SqlServerPersistence.Init(actorSystem);` + +### Configuration + +Both journal and snapshot store share the same configuration keys (however they resides in separate scopes, so they are definied distinctly for either journal or snapshot store): + +- `class` (string with fully qualified type name) - determines class to be used as a persistent journal. Default: *Akka.Persistence.SqlServer.Journal.SqlServerJournal, Akka.Persistence.SqlServer* (for journal) and *Akka.Persistence.SqlServer.Snapshot.SqlServerSnapshotStore, Akka.Persistence.SqlServer* (for snapshot store). +- `plugin-dispatcher` (string with configuration path) - describes a message dispatcher for persistent journal. Default: *akka.actor.default-dispatcher* +- `connection-string` - connection string used to access SQL Server database. Default: *none*. +- `connection-timeout` - timespan determining default connection timeouts on database-related operations. Default: *30s* +- `schema-name` - name of the database schema, where journal or snapshot store tables should be placed. Default: *dbo* +- `table-name` - name of the table used by either journal or snapshot store. Default: *EventJournal* (for journal) or *SnapshotStore* (for snapshot store) +- `auto-initialize` - flag determining if journal or snapshot store related tables should by automatically created when they have not been found in connected database. Default: *false* + +### Custom SQL data queries + +SQL Server persistence plugin defines a default table schema used for both journal and snapshot store. + +**EventJournal table**: + + +---------------+--------+------------+-----------+---------------+----------------+ + | PersistenceId | CS_PID | SequenceNr | IsDeleted | PayloadType | Payload | + +---------------+--------+------------+-----------+---------------+----------------+ + | nvarchar(200) | int | bigint | bit | nvarchar(500) | varbinary(max) | + +---------------+--------+------------+-----------+---------------+----------------+ + +**SnapshotStore table**: + + +---------------+--------+------------+-----------+-----------+---------------+-----------------+ + | PersistenceId | CS_PID | SequenceNr | Timestamp | IsDeleted | SnapshotType | Snapshot | + +---------------+--------+------------+-----------+-----------+---------------+-----------------+ + | nvarchar(200) | int | bigint | datetime2 | bit | nvarchar(500) | varbinary(max) | + +---------------+--------+------------+-----------+-----------+---------------+-----------------+ + +While most of the tables columns maps directly to persistence primitives and are required, CS_PID cached a PersistenceId checksum and server only for performance. + +Underneath Akka.Persistence.SqlServer uses a raw ADO.NET commands. You may choose not to use a dedicated built in ones, but to create your own being better fit for your use case. To do so, you have to create your own versions of `IJournalQueryBuilder` and `IJournalQueryMapper` (for custom journals) or `ISnapshotQueryBuilder` and `ISnapshotQueryMapper` (for custom snapshot store) and then attach inside journal, just like in the example below: + +```csharp +class MyCustomSqlServerJournal: Akka.Persistence.SqlServer.Journal.SqlServerJournal +{ + public MyCustomSqlServerJournal() : base() + { + QueryBuilder = new MyCustomJournalQueryBuilder(); + QueryMapper = new MyCustomJournalQueryMapper(); + } +} +``` + +The final step is to setup your custom journal using akka config: + +``` +akka.persistence.journal.sql-server.class = "MyModule.MyCustomSqlServerJournal, MyModule" +``` \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/Snapshot/QueryBuilder.cs b/src/contrib/persistence/Akka.Persistence.SqlServer/Snapshot/QueryBuilder.cs new file mode 100644 index 00000000000..bc03dc85bf7 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/Snapshot/QueryBuilder.cs @@ -0,0 +1,178 @@ +using System; +using System.Data; +using System.Data.SqlClient; +using System.Text; + +namespace Akka.Persistence.SqlServer.Snapshot +{ + /// + /// Flattened and serialized snapshot object used as intermediate representation + /// before saving snapshot with metadata inside SQL Server database. + /// + public class SnapshotEntry + { + /// + /// Persistence identifier of persistent actor, current snapshot relates to. + /// + public readonly string PersistenceId; + + /// + /// Sequence number used to identify snapshot in it's persistent actor scope. + /// + public readonly long SequenceNr; + + /// + /// Timestamp used to specify date, when the snapshot has been made. + /// + public readonly DateTime Timestamp; + + /// + /// Stringified fully qualified CLR type name of the serialized object. + /// + public readonly string SnapshotType; + + /// + /// Serialized object data. + /// + public readonly byte[] Snapshot; + + public SnapshotEntry(string persistenceId, long sequenceNr, DateTime timestamp, string snapshotType, byte[] snapshot) + { + PersistenceId = persistenceId; + SequenceNr = sequenceNr; + Timestamp = timestamp; + SnapshotType = snapshotType; + Snapshot = snapshot; + } + } + + /// + /// Query builder used for prepare SQL commands used for snapshot store persistence operations. + /// + public interface ISnapshotQueryBuilder + { + /// + /// Deletes a single snapshot identified by it's persistent actor's , + /// and . + /// + SqlCommand DeleteOne(string persistenceId, long sequenceNr, DateTime timestamp); + + /// + /// Deletes all snapshot matching persistent actor's as well as + /// upper (inclusive) bounds of the both and . + /// + SqlCommand DeleteMany(string persistenceId, long maxSequenceNr, DateTime maxTimestamp); + + /// + /// Inserts a single snapshot represented by provided instance. + /// + SqlCommand InsertSnapshot(SnapshotEntry entry); + + /// + /// Selects a single snapshot identified by persistent actor's , + /// matching upper (inclusive) bounds of both and . + /// In case, when more than one snapshot matches specified criteria, one with the highest sequence number will be selected. + /// + SqlCommand SelectSnapshot(string persistenceId, long maxSequenceNr, DateTime maxTimestamp); + } + + internal class DefaultSnapshotQueryBuilder : ISnapshotQueryBuilder + { + private readonly string _deleteSql; + private readonly string _insertSql; + private readonly string _selectSql; + + public DefaultSnapshotQueryBuilder(string schemaName, string tableName) + { + _deleteSql = @"DELETE FROM {0}.{1} WHERE CS_PID = CHECKSUM(@PersistenceId) ".QuoteSchemaAndTable(schemaName, tableName); + _insertSql = @"INSERT INTO {0}.{1} (PersistenceId, SequenceNr, Timestamp, SnapshotType, Snapshot) VALUES (@PersistenceId, @SequenceNr, @Timestamp, @SnapshotType, @Snapshot)".QuoteSchemaAndTable(schemaName, tableName); + _selectSql = @"SELECT PersistenceId, SequenceNr, Timestamp, SnapshotType, Snapshot FROM {0}.{1} WHERE CS_PID = CHECKSUM(@PersistenceId)".QuoteSchemaAndTable(schemaName, tableName); + } + + public SqlCommand DeleteOne(string persistenceId, long sequenceNr, DateTime timestamp) + { + var sqlCommand = new SqlCommand(); + sqlCommand.Parameters.Add(new SqlParameter("@PersistenceId", SqlDbType.NVarChar, persistenceId.Length) { Value = persistenceId }); + var sb = new StringBuilder(_deleteSql); + + if (sequenceNr < long.MaxValue && sequenceNr > 0) + { + sb.Append(@"AND SequenceNr = @SequenceNr "); + sqlCommand.Parameters.Add(new SqlParameter("@SequenceNr", SqlDbType.BigInt) { Value = sequenceNr }); + } + + if (timestamp > DateTime.MinValue && timestamp < DateTime.MaxValue) + { + sb.Append(@"AND Timestamp = @Timestamp"); + sqlCommand.Parameters.Add(new SqlParameter("@Timestamp", SqlDbType.DateTime2) { Value = timestamp }); + } + + sqlCommand.CommandText = sb.ToString(); + + return sqlCommand; + } + + public SqlCommand DeleteMany(string persistenceId, long maxSequenceNr, DateTime maxTimestamp) + { + var sqlCommand = new SqlCommand(); + sqlCommand.Parameters.Add(new SqlParameter("@PersistenceId", SqlDbType.NVarChar, persistenceId.Length) { Value = persistenceId }); + var sb = new StringBuilder(_deleteSql); + + if (maxSequenceNr < long.MaxValue && maxSequenceNr > 0) + { + sb.Append(@" AND SequenceNr <= @SequenceNr "); + sqlCommand.Parameters.Add(new SqlParameter("@SequenceNr", SqlDbType.BigInt) { Value = maxSequenceNr }); + } + + if (maxTimestamp > DateTime.MinValue && maxTimestamp < DateTime.MaxValue) + { + sb.Append(@" AND Timestamp <= @Timestamp"); + sqlCommand.Parameters.Add(new SqlParameter("@Timestamp", SqlDbType.DateTime2) { Value = maxTimestamp }); + } + + sqlCommand.CommandText = sb.ToString(); + + return sqlCommand; + } + + public SqlCommand InsertSnapshot(SnapshotEntry entry) + { + var sqlCommand = new SqlCommand(_insertSql) + { + Parameters = + { + new SqlParameter("@PersistenceId", SqlDbType.NVarChar, entry.PersistenceId.Length) { Value = entry.PersistenceId }, + new SqlParameter("@SequenceNr", SqlDbType.BigInt) { Value = entry.SequenceNr }, + new SqlParameter("@Timestamp", SqlDbType.DateTime2) { Value = entry.Timestamp }, + new SqlParameter("@SnapshotType", SqlDbType.NVarChar, entry.SnapshotType.Length) { Value = entry.SnapshotType }, + new SqlParameter("@Snapshot", SqlDbType.VarBinary, entry.Snapshot.Length) { Value = entry.Snapshot } + } + }; + + return sqlCommand; + } + + public SqlCommand SelectSnapshot(string persistenceId, long maxSequenceNr, DateTime maxTimestamp) + { + var sqlCommand = new SqlCommand(); + sqlCommand.Parameters.Add(new SqlParameter("@PersistenceId", SqlDbType.NVarChar, persistenceId.Length) { Value = persistenceId }); + + var sb = new StringBuilder(_selectSql); + if (maxSequenceNr > 0 && maxSequenceNr < long.MaxValue) + { + sb.Append(" AND SequenceNr <= @SequenceNr "); + sqlCommand.Parameters.Add(new SqlParameter("@SequenceNr", SqlDbType.BigInt) { Value = maxSequenceNr }); + } + + if (maxTimestamp > DateTime.MinValue && maxTimestamp < DateTime.MaxValue) + { + sb.Append(" AND SequenceNr <= @SequenceNr "); + sqlCommand.Parameters.Add(new SqlParameter("@Timestamp", SqlDbType.DateTime2) { Value = maxTimestamp }); + } + + sb.Append(" ORDER BY SequenceNr DESC"); + sqlCommand.CommandText = sb.ToString(); + return sqlCommand; + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/Snapshot/QueryMapper.cs b/src/contrib/persistence/Akka.Persistence.SqlServer/Snapshot/QueryMapper.cs new file mode 100644 index 00000000000..d2547c3208b --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/Snapshot/QueryMapper.cs @@ -0,0 +1,49 @@ +using System; +using System.Data.SqlClient; + +namespace Akka.Persistence.SqlServer.Snapshot +{ + /// + /// Mapper used to map results of snapshot SELECT queries into valid snapshot objects. + /// + public interface ISnapshotQueryMapper + { + /// + /// Map data found under current cursor pointed by SQL data reader into instance. + /// + SelectedSnapshot Map(SqlDataReader reader); + } + + internal class DefaultSnapshotQueryMapper : ISnapshotQueryMapper + { + private readonly Akka.Serialization.Serialization _serialization; + + public DefaultSnapshotQueryMapper(Akka.Serialization.Serialization serialization) + { + _serialization = serialization; + } + + public SelectedSnapshot Map(SqlDataReader reader) + { + var persistenceId = reader.GetString(0); + var sequenceNr = reader.GetInt64(1); + var timestamp = reader.GetDateTime(2); + + var metadata = new SnapshotMetadata(persistenceId, sequenceNr, timestamp); + var snapshot = GetSnapshot(reader); + + return new SelectedSnapshot(metadata, snapshot); + } + + private object GetSnapshot(SqlDataReader reader) + { + var type = Type.GetType(reader.GetString(3), true); + var serializer = _serialization.FindSerializerForType(type); + var binary = (byte[])reader[4]; + + var obj = serializer.FromBinary(binary, type); + + return obj; + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/Snapshot/SqlServerSnapshotStore.cs b/src/contrib/persistence/Akka.Persistence.SqlServer/Snapshot/SqlServerSnapshotStore.cs new file mode 100644 index 00000000000..d69f9819e05 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/Snapshot/SqlServerSnapshotStore.cs @@ -0,0 +1,149 @@ +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Threading; +using System.Threading.Tasks; +using Akka.Persistence.Snapshot; + +namespace Akka.Persistence.SqlServer.Snapshot +{ + /// + /// Actor used for storing incoming snapshots into persistent snapshot store backed by SQL Server database. + /// + public class SqlServerSnapshotStore : SnapshotStore + { + private readonly SqlServerPersistenceExtension _extension; + private SqlConnection _connection; + + protected readonly LinkedList PendingOperations; + + public SqlServerSnapshotStore() + { + _extension = SqlServerPersistence.Instance.Apply(Context.System); + + var settings = _extension.SnapshotStoreSettings; + QueryBuilder = new DefaultSnapshotQueryBuilder(settings.SchemaName, settings.TableName); + QueryMapper = new DefaultSnapshotQueryMapper(Context.System.Serialization); + PendingOperations = new LinkedList(); + } + + /// + /// Query builder used to convert snapshot store related operations into corresponding SQL queries. + /// + public ISnapshotQueryBuilder QueryBuilder { get; set; } + + /// + /// Query mapper used to map SQL query results into snapshots. + /// + public ISnapshotQueryMapper QueryMapper { get; set; } + + protected override void PreStart() + { + base.PreStart(); + + _connection = new SqlConnection(_extension.SnapshotStoreSettings.ConnectionString); + _connection.Open(); + } + + protected override void PostStop() + { + base.PostStop(); + + // stop all operations executed in the background + var node = PendingOperations.First; + while (node != null) + { + var curr = node; + node = node.Next; + + curr.Value.Cancel(); + PendingOperations.Remove(curr); + } + + _connection.Close(); + } + + protected override Task LoadAsync(string persistenceId, SnapshotSelectionCriteria criteria) + { + var sqlCommand = QueryBuilder.SelectSnapshot(persistenceId, criteria.MaxSequenceNr, criteria.MaxTimeStamp); + CompleteCommand(sqlCommand); + + var tokenSource = GetCancellationTokenSource(); + return sqlCommand + .ExecuteReaderAsync(tokenSource.Token) + .ContinueWith(task => + { + var reader = task.Result; + try + { + return reader.Read() ? QueryMapper.Map(reader) : null; + } + finally + { + PendingOperations.Remove(tokenSource); + reader.Close(); + } + }, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.AttachedToParent); + } + + protected override Task SaveAsync(SnapshotMetadata metadata, object snapshot) + { + var entry = ToSnapshotEntry(metadata, snapshot); + var sqlCommand = QueryBuilder.InsertSnapshot(entry); + CompleteCommand(sqlCommand); + + var tokenSource = GetCancellationTokenSource(); + + return sqlCommand.ExecuteNonQueryAsync(tokenSource.Token) + .ContinueWith(task => + { + PendingOperations.Remove(tokenSource); + }, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.AttachedToParent); + } + + protected override void Saved(SnapshotMetadata metadata) { } + + protected override void Delete(SnapshotMetadata metadata) + { + var sqlCommand = QueryBuilder.DeleteOne(metadata.PersistenceId, metadata.SequenceNr, metadata.Timestamp); + CompleteCommand(sqlCommand); + + sqlCommand.ExecuteNonQuery(); + } + + protected override void Delete(string persistenceId, SnapshotSelectionCriteria criteria) + { + var sqlCommand = QueryBuilder.DeleteMany(persistenceId, criteria.MaxSequenceNr, criteria.MaxTimeStamp); + CompleteCommand(sqlCommand); + + sqlCommand.ExecuteNonQuery(); + } + + private void CompleteCommand(SqlCommand command) + { + command.Connection = _connection; + command.CommandTimeout = (int)_extension.SnapshotStoreSettings.ConnectionTimeout.TotalMilliseconds; + } + + private CancellationTokenSource GetCancellationTokenSource() + { + var source = new CancellationTokenSource(); + PendingOperations.AddLast(source); + return source; + } + + private SnapshotEntry ToSnapshotEntry(SnapshotMetadata metadata, object snapshot) + { + var snapshotType = snapshot.GetType(); + var serializer = Context.System.Serialization.FindSerializerForType(snapshotType); + + var binary = serializer.ToBinary(snapshot); + + return new SnapshotEntry( + persistenceId: metadata.PersistenceId, + sequenceNr: metadata.SequenceNr, + timestamp: metadata.Timestamp, + snapshotType: snapshotType.QualifiedTypeName(), + snapshot: binary); + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/SqlServerInitializer.cs b/src/contrib/persistence/Akka.Persistence.SqlServer/SqlServerInitializer.cs new file mode 100644 index 00000000000..57189749348 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/SqlServerInitializer.cs @@ -0,0 +1,93 @@ +using System; +using System.Data.SqlClient; + +namespace Akka.Persistence.SqlServer +{ + internal static class SqlServerInitializer + { + private const string SqlJournalFormat = @" + IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '{2}' AND TABLE_NAME = '{3}') + BEGIN + CREATE TABLE {0}.{1} ( + PersistenceID NVARCHAR(200) NOT NULL, + CS_PID AS CHECKSUM(PersistenceID), + SequenceNr BIGINT NOT NULL, + IsDeleted BIT NOT NULL, + PayloadType NVARCHAR(500) NOT NULL, + Payload VARBINARY(MAX) NOT NULL + CONSTRAINT PK_{3} PRIMARY KEY (PersistenceID, SequenceNr) + ); + CREATE INDEX IX_{3}_CS_PID ON {0}.{1}(CS_PID); + CREATE INDEX IX_{3}_SequenceNr ON {0}.{1}(SequenceNr); + END + "; + + private const string SqlSnapshotStoreFormat = @" + IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '{2}' AND TABLE_NAME = '{3}') + BEGIN + CREATE TABLE {0}.{1} ( + PersistenceID NVARCHAR(200) NOT NULL, + CS_PID AS CHECKSUM(PersistenceID), + SequenceNr BIGINT NOT NULL, + Timestamp DATETIME2 NOT NULL, + SnapshotType NVARCHAR(500) NOT NULL, + Snapshot VARBINARY(MAX) NOT NULL + CONSTRAINT PK_{3} PRIMARY KEY (PersistenceID, SequenceNr) + ); + CREATE INDEX IX_{3}_CS_PID ON {0}.{1}(CS_PID); + CREATE INDEX IX_{3}_SequenceNr ON {0}.{1}(SequenceNr); + CREATE INDEX IX_{3}_Timestamp ON {0}.{1}(Timestamp); + END + "; + + /// + /// Initializes a SQL Server journal-related tables according to 'schema-name', 'table-name' + /// and 'connection-string' values provided in 'akka.persistence.journal.sql-server' config. + /// + internal static void CreateSqlServerJournalTables(string connectionString, string schemaName, string tableName) + { + var sql = InitJournalSql(tableName, schemaName); + ExecuteSql(connectionString, sql); + } + + /// + /// Initializes a SQL Server snapshot store related tables according to 'schema-name', 'table-name' + /// and 'connection-string' values provided in 'akka.persistence.snapshot-store.sql-server' config. + /// + internal static void CreateSqlServerSnapshotStoreTables(string connectionString, string schemaName, string tableName) + { + var sql = InitSnapshotStoreSql(tableName, schemaName); + ExecuteSql(connectionString, sql); + } + + private static string InitJournalSql(string tableName, string schemaName = null) + { + if (string.IsNullOrEmpty(tableName)) throw new ArgumentNullException("tableName", "Akka.Persistence.SqlServer journal table name is required"); + schemaName = schemaName ?? "dbo"; + + var cb = new SqlCommandBuilder(); + return string.Format(SqlJournalFormat, cb.QuoteIdentifier(schemaName), cb.QuoteIdentifier(tableName), cb.UnquoteIdentifier(schemaName), cb.UnquoteIdentifier(tableName)); + } + + private static string InitSnapshotStoreSql(string tableName, string schemaName = null) + { + if (string.IsNullOrEmpty(tableName)) throw new ArgumentNullException("tableName", "Akka.Persistence.SqlServer snapshot store table name is required"); + schemaName = schemaName ?? "dbo"; + + var cb = new SqlCommandBuilder(); + return string.Format(SqlSnapshotStoreFormat, cb.QuoteIdentifier(schemaName), cb.QuoteIdentifier(tableName), cb.UnquoteIdentifier(schemaName), cb.UnquoteIdentifier(tableName)); + } + + private static void ExecuteSql(string connectionString, string sql) + { + using (var conn = new SqlConnection(connectionString)) + using (var command = conn.CreateCommand()) + { + conn.Open(); + + command.CommandText = sql; + command.ExecuteNonQuery(); + } + } + } +} \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/app.config b/src/contrib/persistence/Akka.Persistence.SqlServer/app.config new file mode 100644 index 00000000000..3e4f9f30e83 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/app.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/contrib/persistence/Akka.Persistence.SqlServer/sql-server.conf b/src/contrib/persistence/Akka.Persistence.SqlServer/sql-server.conf new file mode 100644 index 00000000000..d7fcde9e241 --- /dev/null +++ b/src/contrib/persistence/Akka.Persistence.SqlServer/sql-server.conf @@ -0,0 +1,54 @@ +akka.persistence{ + + journal { + sql-server { + + # qualified type name of the SQL Server persistence journal actor + class = "Akka.Persistence.SqlServer.Journal.SqlServerJournal, Akka.Persistence.SqlServer" + + # dispatcher used to drive journal actor + plugin-dispatcher = "akka.actor.default-dispatcher" + + # connection string used for database access + connection-string = "" + + # default SQL commands timeout + connection-timeout = 30s + + # SQL server schema name to table corresponding with persistent journal + schema-name = dbo + + # SQL server table corresponding with persistent journal + table-name = EventJournal + + # should corresponding journal table be initialized automatically + auto-initialize = off + } + } + + snapshot-store { + sql-server { + + # qualified type name of the SQL Server persistence journal actor + class = "Akka.Persistence.SqlServer.Snapshot.SqlServerSnapshotStore, Akka.Persistence.SqlServer" + + # dispatcher used to drive journal actor + plugin-dispatcher = ""akka.actor.default-dispatcher"" + + # connection string used for database access + connection-string = "" + + # default SQL commands timeout + connection-timeout = 30s + + # SQL server schema name to table corresponding with persistent journal + schema-name = dbo + + # SQL server table corresponding with persistent journal + table-name = SnapshotStore + + # should corresponding journal table be initialized automatically + auto-initialize = off + } + } +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.NUnit.Tests/Akka.TestKit.NUnit.Tests.csproj b/src/contrib/testkits/Akka.TestKit.NUnit.Tests/Akka.TestKit.NUnit.Tests.csproj new file mode 100644 index 00000000000..55fefe13f21 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit.Tests/Akka.TestKit.NUnit.Tests.csproj @@ -0,0 +1,83 @@ + + + + + Debug + AnyCPU + {D63223FA-03F5-4B32-A6EC-668F718C0826} + Library + Properties + Akka.TestKit.NUnit.Tests + Akka.TestKit.NUnit.Tests + v4.5 + 512 + ..\..\..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll + + + + + + + + + + + + + + + + + + + + {0D3CBAD0-BBDB-43E5-AFC4-ED1D3ECDC224} + Akka.TestKit + + + {5DEDDF90-37F0-48D3-A0B0-A5CBD8A7E377} + Akka + + + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E} + Akka.TestKit.NUnit + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.NUnit.Tests/AssertionsTests.cs b/src/contrib/testkits/Akka.TestKit.NUnit.Tests/AssertionsTests.cs new file mode 100644 index 00000000000..e9e7f5fc5f7 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit.Tests/AssertionsTests.cs @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2015 Typesafe Inc. +// Copyright (C) 2013-2015 Akka.NET project +// +//---------------------------------------------------------------------- + +using NUnit.Framework; + +namespace Akka.TestKit.NUnit.Tests +{ + [TestFixture] + public class AssertionsTests + { + private readonly NUnitAssertions _assertions; + + public AssertionsTests() + { + _assertions = new NUnitAssertions(); + } + + [Test] + [ExpectedException(typeof(AssertionException))] + public void Fail_should_throw() + { + _assertions.Fail(); + } + + [Test] + [ExpectedException(typeof(AssertionException))] + public void AssertTrue_should_throw_on_false() + { + _assertions.AssertTrue(false); + } + + [Test] + public void AssertTrue_should_succeed_on_true() + { + _assertions.AssertTrue(true); + } + + [Test] + [ExpectedException(typeof(AssertionException))] + public void AssertFalse_should_throw_on_true() + { + _assertions.AssertFalse(true); + } + + [Test] + public void AssertFalse_should_succeed_on_false() + { + _assertions.AssertFalse(false); + } + + + [Test] + [ExpectedException(typeof(AssertionException))] + public void AssertEqual_should_throw_on_not_equal() + { + _assertions.AssertEqual(42, 4711); + } + + [Test] + public void AssertEqual_should_succeed_on_equal() + { + _assertions.AssertEqual(42, 42); + } + + + [Test] + [ExpectedException(typeof(AssertionException))] + public void AssertEqualWithComparer_should_throw_on_not_equal() + { + _assertions.AssertEqual(42, 42, (x, y) => false); + } + + [Test] + public void AssertEqualWithComparer_should_succeed_on_equal() + { + _assertions.AssertEqual(42, 4711, (x, y) => true); + } + } +} diff --git a/src/contrib/testkits/Akka.TestKit.NUnit.Tests/Properties/AssemblyInfo.cs b/src/contrib/testkits/Akka.TestKit.NUnit.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..e407ce6962a --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Akka.TestKit.NUnit.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Akka.TestKit.NUnit.Tests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ae11b4b3-d664-4e2d-b8a0-a2a2bf71674a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/contrib/testkits/Akka.TestKit.NUnit.Tests/TestKitTests.cs b/src/contrib/testkits/Akka.TestKit.NUnit.Tests/TestKitTests.cs new file mode 100644 index 00000000000..65f65964dcc --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit.Tests/TestKitTests.cs @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2015 Typesafe Inc. +// Copyright (C) 2013-2015 Akka.NET project +// +//---------------------------------------------------------------------- + +using Akka.Actor; +using NUnit.Framework; + +namespace Akka.TestKit.NUnit.Tests +{ + [TestFixture] + public class TestKitTests : TestKit + { + [TearDown] + public void Cleanup() + { + Shutdown(); + } + + [Test] + public void Expect_a_message() + { + TestActor.Tell("Test"); + ExpectMsg("Test"); + } + } +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.NUnit.Tests/packages.config b/src/contrib/testkits/Akka.TestKit.NUnit.Tests/packages.config new file mode 100644 index 00000000000..c714ef3a23e --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit.Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.NUnit/Akka.TestKit.NUnit.csproj b/src/contrib/testkits/Akka.TestKit.NUnit/Akka.TestKit.NUnit.csproj new file mode 100644 index 00000000000..366fcd95e1e --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit/Akka.TestKit.NUnit.csproj @@ -0,0 +1,83 @@ + + + + + Debug + AnyCPU + {BEF84A6F-32C4-4ACF-AFC3-7B5FCA6F209E} + Library + Properties + Akka.TestKit.NUnit + Akka.TestKit.NUnit + v4.5 + 512 + ..\..\..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\Akka.TestKit.NUnit.xml + + + + ..\..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll + + + + + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + + + + {0D3CBAD0-BBDB-43E5-AFC4-ED1D3ECDC224} + Akka.TestKit + + + {5DEDDF90-37F0-48D3-A0B0-A5CBD8A7E377} + Akka + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.NUnit/Akka.TestKit.NUnit.nuspec b/src/contrib/testkits/Akka.TestKit.NUnit/Akka.TestKit.NUnit.nuspec new file mode 100644 index 00000000000..d5cd5783336 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit/Akka.TestKit.NUnit.nuspec @@ -0,0 +1,20 @@ + + + + @project@ + @project@@title@ + @build.number@ + @authors@ + @authors@ + TestKit for writing tests for Akka.NET using NUnit. + https://github.com/akkadotnet/akka.net/blob/master/LICENSE + https://github.com/akkadotnet/akka.net + http://getakka.net/images/AkkaNetLogo.Normal.png + false + @releaseNotes@ + @copyright@ + @tags@ NUnit + @dependencies@ + @references@ + + diff --git a/src/contrib/testkits/Akka.TestKit.NUnit/NUnitAssertions.cs b/src/contrib/testkits/Akka.TestKit.NUnit/NUnitAssertions.cs new file mode 100644 index 00000000000..28ee8d86618 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit/NUnitAssertions.cs @@ -0,0 +1,50 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2015 Typesafe Inc. +// Copyright (C) 2013-2015 Akka.NET project +// +//----------------------------------------------------------------------- + +using System; +using NUnit.Framework; + +namespace Akka.TestKit.NUnit +{ + /// + /// Assertions for NUnit + /// + public class NUnitAssertions : ITestKitAssertions + { + + public void Fail(string format = "", params object[] args) + { + Assert.Fail(format, args); + } + + public void AssertTrue(bool condition, string format = "", params object[] args) + { + Assert.IsTrue(condition, format, args); + } + + public void AssertFalse(bool condition, string format = "", params object[] args) + { + Assert.IsFalse(condition, format, args); + } + + public void AssertEqual(T expected, T actual, string format = "", params object[] args) + { + Assert.AreEqual(expected, actual, format, args); + } + + public void AssertEqual(T expected, T actual, Func comparer, string format = "", params object[] args) + { + if (!comparer(expected, actual)) + throw new AssertionException(string.Format("Assert.AreEqual failed. Expected [{0}]. Actual [{1}]. {2}", FormatValue(expected), FormatValue(actual), string.Format(format, args))); + } + + private static string FormatValue(T expected) + { + return ReferenceEquals(expected, null) ? "null" : expected.ToString(); + } + } +} \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.NUnit/Properties/AssemblyInfo.cs b/src/contrib/testkits/Akka.TestKit.NUnit/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..c21eca0815f --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Akka.TestKit.NUnit")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Akka.TestKit.NUnit")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d2d62196-5099-4e19-a239-a055ae941532")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] diff --git a/src/contrib/testkits/Akka.TestKit.NUnit/TestKit.cs b/src/contrib/testkits/Akka.TestKit.NUnit/TestKit.cs new file mode 100644 index 00000000000..0092e33e770 --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit/TestKit.cs @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2015 Typesafe Inc. +// Copyright (C) 2013-2015 Akka.NET project +// +//----------------------------------------------------------------------- + +using System; +using Akka.Actor; +using Akka.Configuration; + +namespace Akka.TestKit.NUnit +{ + /// + /// TestKit for NUnit. + /// + public class TestKit : TestKitBase, IDisposable + { + private static readonly NUnitAssertions _assertions = new NUnitAssertions(); + private bool _isDisposed; //Automatically initialized to false; + + /// + /// Create a new instance of the for NUnit class. + /// If no is passed in, a new system + /// with will be created. + /// + /// Optional: The actor system. + public TestKit(ActorSystem system = null) + : base(_assertions, system) + { + //Intentionally left blank + } + + /// + /// Create a new instance of the for NUnit class. + /// A new system with the specified configuration will be created. + /// + /// The configuration to use for the system. + /// Optional: the name of the system. Default: "test" + public TestKit(Config config, string actorSystemName = null) + : base(_assertions, config, actorSystemName) + { + //Intentionally left blank + } + + + /// + /// Create a new instance of the for NUnit class. + /// A new system with the specified configuration will be created. + /// + /// The configuration to use for the system. + public TestKit(string config) + : base(_assertions, ConfigurationFactory.ParseString(config)) + { + //Intentionally left blank + } + + public new static Config DefaultConfig { get { return TestKitBase.DefaultConfig; } } + public new static Config FullDebugConfig { get { return TestKitBase.FullDebugConfig; } } + + protected static NUnitAssertions Assertions { get { return _assertions; } } + + + /// + /// This method is called when a test ends. + /// If you override this, make sure you either call + /// base.AfterTest() or TestKitBase.Shutdown to shut down + /// the system. Otherwise you'll leak memory. + /// + /// + protected virtual void AfterAll() + { + Shutdown(); + } + + + // Dispose ------------------------------------------------------------ + + //Destructor: + //~TestKit() + //{ + // // Finalizer calls Dispose(false) + // Dispose(false); + //} + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + Dispose(true); + //Take this object off the finalization queue and prevent finalization code for this object + //from executing a second time. + GC.SuppressFinalize(this); + } + + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// if set to true the method has been called directly or indirectly by a + /// user's code. Managed and unmanaged resources will be disposed.
+ /// if set to false the method has been called by the runtime from inside the finalizer and only + /// unmanaged resources can be disposed. + protected virtual void Dispose(bool disposing) + { + // If disposing equals false, the method has been called by the + // runtime from inside the finalizer and you should not reference + // other objects. Only unmanaged resources can be disposed. + + try + { + //Make sure Dispose does not get called more than once, by checking the disposed field + if (!_isDisposed) + { + if (disposing) + { + AfterAll(); + } + } + _isDisposed = true; + } + finally + { + // base.dispose(disposing); + } + } + } +} diff --git a/src/contrib/testkits/Akka.TestKit.NUnit/packages.config b/src/contrib/testkits/Akka.TestKit.NUnit/packages.config new file mode 100644 index 00000000000..c714ef3a23e --- /dev/null +++ b/src/contrib/testkits/Akka.TestKit.NUnit/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/contrib/testkits/Akka.TestKit.VsTest/Akka.TestKit.VsTest.nuspec b/src/contrib/testkits/Akka.TestKit.VsTest/Akka.TestKit.VsTest.nuspec index 71ff019830a..004a4368ec1 100644 --- a/src/contrib/testkits/Akka.TestKit.VsTest/Akka.TestKit.VsTest.nuspec +++ b/src/contrib/testkits/Akka.TestKit.VsTest/Akka.TestKit.VsTest.nuspec @@ -9,7 +9,7 @@ TestKit for writing tests for Akka.NET using Visual Studio Unit Testing Framework. https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -20,4 +20,4 @@ - \ No newline at end of file + diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/Akka.TestKit.Xunit.nuspec b/src/contrib/testkits/Akka.TestKit.Xunit/Akka.TestKit.Xunit.nuspec index e26ea1fdedf..968416fb406 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/Akka.TestKit.Xunit.nuspec +++ b/src/contrib/testkits/Akka.TestKit.Xunit/Akka.TestKit.Xunit.nuspec @@ -9,7 +9,7 @@ TestKit for writing tests for Akka.NET using xUnit. https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/contrib/testkits/Akka.TestKit.Xunit/TestKit.cs b/src/contrib/testkits/Akka.TestKit.Xunit/TestKit.cs index d1c27298cc9..4ad5fb4c3bf 100644 --- a/src/contrib/testkits/Akka.TestKit.Xunit/TestKit.cs +++ b/src/contrib/testkits/Akka.TestKit.Xunit/TestKit.cs @@ -97,7 +97,7 @@ public void Dispose() /// user's code. Managed and unmanaged resources will be disposed.
/// if set to false the method has been called by the runtime from inside the finalizer and only /// unmanaged resources can be disposed. - private void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) { // If disposing equals false, the method has been called by the // runtime from inside the finalizer and you should not reference diff --git a/src/core/Akka.Cluster.Tests/ClusterConfigSpec.cs b/src/core/Akka.Cluster.Tests/ClusterConfigSpec.cs index de0a006c7e7..4477e38798a 100644 --- a/src/core/Akka.Cluster.Tests/ClusterConfigSpec.cs +++ b/src/core/Akka.Cluster.Tests/ClusterConfigSpec.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Immutable; using Akka.Actor; -using Akka.Dispatch; using Akka.Remote; using Akka.TestKit; using Xunit; @@ -47,7 +46,7 @@ public void ClusteringMustBeAbleToParseGenericClusterConfigElements() //TODO: //Assert.AreEqual(ImmutableDictionary.Create(), settings.); Assert.Equal(ImmutableHashSet.Create(), settings.Roles); - Assert.Equal(Dispatchers.DefaultDispatcherId, settings.UseDispatcher); + Assert.Equal("akka.cluster.default-cluster-dispatcher", settings.UseDispatcher); Assert.Equal(.8, settings.GossipDifferentViewProbability); Assert.Equal(400, settings.ReduceGossipDifferentViewProbability); Assert.Equal(TimeSpan.FromMilliseconds(33), settings.SchedulerTickDuration); @@ -58,6 +57,18 @@ public void ClusteringMustBeAbleToParseGenericClusterConfigElements() Assert.Equal(TimeSpan.FromSeconds(3), settings.MetricsGossipInterval); Assert.Equal(TimeSpan.FromSeconds(12), settings.MetricsMovingAverageHalfLife); } - } -} + [Fact] + public void ClusteringShouldHaveCorrectDefaultForkJoinDispatcher() + { + var dispatchConfig = Sys.Settings.Config.GetConfig("akka.cluster.default-cluster-dispatcher"); + var dispatcherThreadPoolSettings = dispatchConfig.GetConfig("dedicated-thread-pool"); + + var dispatcherType = dispatchConfig.GetString("type"); + var threadCount = dispatcherThreadPoolSettings.GetInt("thread-count"); + + Assert.Equal("ForkJoinDispatcher", dispatcherType); + Assert.Equal(4, threadCount); + } + } +} \ No newline at end of file diff --git a/src/core/Akka.Cluster.Tests/ClusterHeartBeatSenderStateSpec.cs b/src/core/Akka.Cluster.Tests/ClusterHeartBeatSenderStateSpec.cs index 434a283954c..e96d0062db9 100644 --- a/src/core/Akka.Cluster.Tests/ClusterHeartBeatSenderStateSpec.cs +++ b/src/core/Akka.Cluster.Tests/ClusterHeartBeatSenderStateSpec.cs @@ -266,7 +266,7 @@ public void ClusterHeartbeatSenderState_must_behave_correctly_for_random_operati break; } } - catch (Exception ex) + catch (Exception) { Debug.WriteLine("Failure context: i = {0}, node = {1}, op={2}, unreachable={3}, ringReceivers={4}, ringNodes={5}", i, node, operation, string.Join(",",state.Unreachable), diff --git a/src/core/Akka.Cluster.Tests/EWMASpec.cs b/src/core/Akka.Cluster.Tests/EWMASpec.cs index c5294a5a703..53541ad6dfd 100644 --- a/src/core/Akka.Cluster.Tests/EWMASpec.cs +++ b/src/core/Akka.Cluster.Tests/EWMASpec.cs @@ -126,19 +126,13 @@ public void Calculate_the_EWMA_for_multiple_variable_datastreams() #region IDisposable members - public void Dispose() + /// + /// Needs to hide previous Dispose implementation in order to avoid recursive disposal. + /// + public new void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _collector.Dispose(); - base.Dispose(); - } + _collector.Dispose(); + base.Dispose(); } #endregion diff --git a/src/core/Akka.Cluster.Tests/MetricsCollectorSpec.cs b/src/core/Akka.Cluster.Tests/MetricsCollectorSpec.cs index c6ed58048ff..32f45f65717 100644 --- a/src/core/Akka.Cluster.Tests/MetricsCollectorSpec.cs +++ b/src/core/Akka.Cluster.Tests/MetricsCollectorSpec.cs @@ -119,19 +119,13 @@ public void MetricsCollector_collect_50_node_metrics_samples_in_an_acceptable_du #region IDisposable members - public void Dispose() + /// + /// Needs to hide previous Dispose implementation in order to avoid recursive disposal. + /// + public new void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _collector.Dispose(); - base.Dispose(); - } + _collector.Dispose(); + base.Dispose(); } #endregion diff --git a/src/core/Akka.Cluster.Tests/MultiNode/ClusterDeathWatchSpec.cs b/src/core/Akka.Cluster.Tests/MultiNode/ClusterDeathWatchSpec.cs index e3d56fa2fa2..c2532ed09a5 100644 --- a/src/core/Akka.Cluster.Tests/MultiNode/ClusterDeathWatchSpec.cs +++ b/src/core/Akka.Cluster.Tests/MultiNode/ClusterDeathWatchSpec.cs @@ -291,13 +291,13 @@ public void AnActorWatchingARemoteActorInTheClusterMustBeAbleToShutdownSystemWhe { Sys.AwaitTermination(timeout); } - catch (TimeoutException ex) + catch (TimeoutException) { Assert.True(false, String.Format("Failed to stop [{0}] within [{1}]", Sys.Name, timeout)); } - // signal to the first node that the fourth nodeis done + // signal to the first node that the fourth node is done var endSystem = ActorSystem.Create("EndSystem", Sys.Settings.Config); try { diff --git a/src/core/Akka.Cluster/Akka.Cluster.nuspec b/src/core/Akka.Cluster/Akka.Cluster.nuspec index a695c848c03..c35ac424372 100644 --- a/src/core/Akka.Cluster/Akka.Cluster.nuspec +++ b/src/core/Akka.Cluster/Akka.Cluster.nuspec @@ -9,7 +9,7 @@ Cluster support for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/core/Akka.Cluster/Cluster.cs b/src/core/Akka.Cluster/Cluster.cs index 0b37f8fb782..cb8f21271d4 100644 --- a/src/core/Akka.Cluster/Cluster.cs +++ b/src/core/Akka.Cluster/Cluster.cs @@ -205,7 +205,7 @@ public void Leave(Address address) } /// - /// Send command to DOWN the ndoe specified by . + /// Send command to DOWN the node specified by . /// /// When a member is considered by the failure detector to be unreachable the leader is not /// allowed to perform its duties, such as changing status of new joining members to . diff --git a/src/core/Akka.Cluster/ClusterDaemon.cs b/src/core/Akka.Cluster/ClusterDaemon.cs index e8747f1f155..6e83f3347b2 100644 --- a/src/core/Akka.Cluster/ClusterDaemon.cs +++ b/src/core/Akka.Cluster/ClusterDaemon.cs @@ -1065,8 +1065,7 @@ public void Joining(UniqueAddress node, ImmutableHashSet roles) UpdateLatestGossip(newGossip); - _log.Info("Node [{0}] is JOINING, roles [{1}]", node.Address, - roles.Select(r => r.ToString()).Aggregate("", (a, b) => a + ", " + b)); + _log.Info("Node [{0}] is JOINING, roles [{1}]", node.Address, string.Join(",", roles)); if (!node.Equals(SelfUniqueAddress)) { diff --git a/src/core/Akka.Cluster/ClusterEvent.cs b/src/core/Akka.Cluster/ClusterEvent.cs index 1ab23da0df4..9bf2da3948f 100644 --- a/src/core/Akka.Cluster/ClusterEvent.cs +++ b/src/core/Akka.Cluster/ClusterEvent.cs @@ -9,7 +9,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Runtime.InteropServices; using Akka.Actor; using Akka.Event; using Akka.Util.Internal; diff --git a/src/core/Akka.Cluster/ClusterHeartbeat.cs b/src/core/Akka.Cluster/ClusterHeartbeat.cs index d15bb4d780a..38e5b99372d 100644 --- a/src/core/Akka.Cluster/ClusterHeartbeat.cs +++ b/src/core/Akka.Cluster/ClusterHeartbeat.cs @@ -63,7 +63,7 @@ public ClusterHeartbeatSender() ImmutableHashSet.Create(), FailureDetector); - //stat perioidic heartbeat to other nodes in cluster + //start periodic heartbeat to other nodes in cluster _heartbeatTask = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable( _cluster.Settings.PeriodicTasksInitialDelay.Max(_cluster.Settings.HeartbeatInterval), diff --git a/src/core/Akka.Cluster/ClusterMetricsCollector.cs b/src/core/Akka.Cluster/ClusterMetricsCollector.cs index d6c832dd0f2..6d22fdbcbf1 100644 --- a/src/core/Akka.Cluster/ClusterMetricsCollector.cs +++ b/src/core/Akka.Cluster/ClusterMetricsCollector.cs @@ -747,11 +747,15 @@ public PerformanceCounterMetricsCollector(ActorSystem system) : this(Cluster.Get private PerformanceCounter _systemLoadAverageCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", true); private PerformanceCounter _systemAvailableMemory = new PerformanceCounter("Memory", "Available MBytes", true); -#if MONO + private static readonly bool IsRunningOnMono = Type.GetType("Mono.Runtime") != null; + + // Mono doesn't support Microsoft.VisualBasic, so need an alternative way of sampling this value // see http://stackoverflow.com/questions/105031/how-do-you-get-total-amount-of-ram-the-computer-has - private PerformanceCounter _systemMaxMemory = new PerformanceCounter("Mono Memory", "Total Physical Memory"); -#endif + private PerformanceCounter _monoSystemMaxMemory = IsRunningOnMono + ? new PerformanceCounter("Mono Memory", "Total Physical Memory") + : null; + #endregion @@ -813,14 +817,20 @@ public Metric SystemMemoryAvailable() /// public Metric SystemMaxMemory() { - return Metric.Create(StandardMetrics.SystemMemoryMax, -#if MONO - _systemMaxMemory.RawValue + return Metric.Create(StandardMetrics.SystemMemoryMax, + IsRunningOnMono + ? _monoSystemMaxMemory.RawValue + : GetVbTotalPhysicalMemory()); + } + + double GetVbTotalPhysicalMemory() + { +#if __MonoCS__ + throw new NotImplementedException(); #else - new Microsoft.VisualBasic.Devices.ComputerInfo().TotalPhysicalMemory + return new Microsoft.VisualBasic.Devices.ComputerInfo().TotalPhysicalMemory; #endif - ); - } + } #endregion diff --git a/src/core/Akka.Cluster/Configuration/Cluster.conf b/src/core/Akka.Cluster/Configuration/Cluster.conf index f8003f0f701..f2a42ac6629 100644 --- a/src/core/Akka.Cluster/Configuration/Cluster.conf +++ b/src/core/Akka.Cluster/Configuration/Cluster.conf @@ -83,7 +83,7 @@ akka { # The id of the dispatcher to use for cluster actors. If not specified # default dispatcher is used. # If specified you need to define the settings of the actual dispatcher. - use-dispatcher = "" + use-dispatcher = "akka.cluster.default-cluster-dispatcher" # Gossip to random node with newer or older state information, if any with # this probability. Otherwise Gossip to any random live node. @@ -178,6 +178,14 @@ akka { ticks-per-wheel = 512 } + default-cluster-dispatcher { + type = ForkJoinDispatcher + dedicated-thread-pool { + # Fixed number of threads to have in this threadpool + thread-count = 4 + } + } + } # Default configuration for routers @@ -229,5 +237,4 @@ akka { adaptive-group = "akka.cluster.routing.AdaptiveLoadBalancingGroup" } } - } \ No newline at end of file diff --git a/src/core/Akka.FSharp/Akka.FSharp.nuspec b/src/core/Akka.FSharp/Akka.FSharp.nuspec index e90bc8751a3..f92395c7d1a 100644 --- a/src/core/Akka.FSharp/Akka.FSharp.nuspec +++ b/src/core/Akka.FSharp/Akka.FSharp.nuspec @@ -9,7 +9,7 @@ F# API support for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/core/Akka.FSharp/Properties/AssemblyInfo.fs b/src/core/Akka.FSharp/Properties/AssemblyInfo.fs index 32507935255..f24ef9690fa 100644 --- a/src/core/Akka.FSharp/Properties/AssemblyInfo.fs +++ b/src/core/Akka.FSharp/Properties/AssemblyInfo.fs @@ -10,9 +10,9 @@ open System.Runtime.InteropServices [] [] [] -[] -[] +[] +[] do () module internal AssemblyVersionInformation = - let [] Version = "1.0.0.0" + let [] Version = "1.0.1.0" diff --git a/src/core/Akka.FSharp/Schedulers.fs b/src/core/Akka.FSharp/Schedulers.fs index 61ffd058912..bb58f8e8617 100644 --- a/src/core/Akka.FSharp/Schedulers.fs +++ b/src/core/Akka.FSharp/Schedulers.fs @@ -47,15 +47,8 @@ type Akka.Actor.ITellScheduler with /// Interval. /// Message to be sent to the receiver by the scheduler. /// Message receiver. - /// Optional actor reference set up as message sender - /// Optional cancelation token - member this.ScheduleTellRepeatedly(after: TimeSpan, every: TimeSpan, receiver: IActorRef, message: 'Message, ?sender: IActorRef, ?cancelable: ICancelable) : unit = - let s = match sender with - | Some aref -> aref - | None -> ActorCell.GetCurrentSelfOrNoSender() - match cancelable with - | Some c -> this.ScheduleTellRepeatedly(after, every, receiver, message, s, c) - | None -> this.ScheduleTellRepeatedly(after, every, receiver, message, s) + member this.ScheduleTellRepeatedly(after: TimeSpan, every: TimeSpan, receiver: IActorRef, message: 'Message) : unit = + this.ScheduleTellRepeatedly(after, every, receiver, message, ActorRefs.NoSender) /// /// Schedules a single send to the provided . @@ -63,13 +56,6 @@ type Akka.Actor.ITellScheduler with /// Delay before sending a message. /// Message to be sent to the receiver by the scheduler. /// Message receiver. - /// Optional actor reference set up as message sender - /// Optional cancelation token - member this.ScheduleTellOnce(after: TimeSpan, receiver: IActorRef, message: 'Message, ?sender: IActorRef, ?cancelable: ICancelable) : unit = - let s = match sender with - | Some aref -> aref - | None -> ActorCell.GetCurrentSelfOrNoSender() - match cancelable with - | Some c -> this.ScheduleTellOnce(after, receiver, message, s, c) - | None -> this.ScheduleTellOnce(after, receiver, message, s) + member this.ScheduleTellOnce(after: TimeSpan, receiver: IActorRef, message: 'Message) : unit = + this.ScheduleTellOnce(after, receiver, message, ActorRefs.NoSender) diff --git a/src/core/Akka.MultiNodeTestRunner.Shared.Tests/Akka.MultiNodeTestRunner.Shared.Tests.csproj b/src/core/Akka.MultiNodeTestRunner.Shared.Tests/Akka.MultiNodeTestRunner.Shared.Tests.csproj index b81f820af06..681e917ae99 100644 --- a/src/core/Akka.MultiNodeTestRunner.Shared.Tests/Akka.MultiNodeTestRunner.Shared.Tests.csproj +++ b/src/core/Akka.MultiNodeTestRunner.Shared.Tests/Akka.MultiNodeTestRunner.Shared.Tests.csproj @@ -37,11 +37,7 @@ - - - - ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll diff --git a/src/core/Akka.MultiNodeTestRunner.Shared/Akka.MultiNodeTestRunner.Shared.csproj b/src/core/Akka.MultiNodeTestRunner.Shared/Akka.MultiNodeTestRunner.Shared.csproj index 76f80b39906..ada28950df1 100644 --- a/src/core/Akka.MultiNodeTestRunner.Shared/Akka.MultiNodeTestRunner.Shared.csproj +++ b/src/core/Akka.MultiNodeTestRunner.Shared/Akka.MultiNodeTestRunner.Shared.csproj @@ -34,11 +34,7 @@ - - - - ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll diff --git a/src/core/Akka.MultiNodeTestRunner.Shared/Persistence/JsonPersistentTestRunStore.cs b/src/core/Akka.MultiNodeTestRunner.Shared/Persistence/JsonPersistentTestRunStore.cs index b794c2c6ef6..bd6826fb767 100644 --- a/src/core/Akka.MultiNodeTestRunner.Shared/Persistence/JsonPersistentTestRunStore.cs +++ b/src/core/Akka.MultiNodeTestRunner.Shared/Persistence/JsonPersistentTestRunStore.cs @@ -10,14 +10,13 @@ using System.Reflection; using System.Text; using Akka.MultiNodeTestRunner.Shared.Reporting; -using Akka.Util; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace Akka.MultiNodeTestRunner.Shared.Persistence { /// - /// XML (omg not XML!) implementation of the + /// JavaScript Object Notation (JSON) implementation of the /// public class JsonPersistentTestRunStore : IPersistentTestRunStore { diff --git a/src/core/Akka.MultiNodeTestRunner.Shared/Reporting/TestRunCoordinator.cs b/src/core/Akka.MultiNodeTestRunner.Shared/Reporting/TestRunCoordinator.cs index 35a02f2c241..c42d7fe2746 100644 --- a/src/core/Akka.MultiNodeTestRunner.Shared/Reporting/TestRunCoordinator.cs +++ b/src/core/Akka.MultiNodeTestRunner.Shared/Reporting/TestRunCoordinator.cs @@ -10,7 +10,6 @@ using System.Linq; using Akka.Actor; using Akka.MultiNodeTestRunner.Shared.Sinks; -using Akka.Util; namespace Akka.MultiNodeTestRunner.Shared.Reporting { diff --git a/src/core/Akka.MultiNodeTestRunner.Shared/Sinks/MessageSink.cs b/src/core/Akka.MultiNodeTestRunner.Shared/Sinks/MessageSink.cs index 4eefdca2e15..fa925b2a829 100644 --- a/src/core/Akka.MultiNodeTestRunner.Shared/Sinks/MessageSink.cs +++ b/src/core/Akka.MultiNodeTestRunner.Shared/Sinks/MessageSink.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Akka.Actor; using Akka.Event; -using Akka.Util; namespace Akka.MultiNodeTestRunner.Shared.Sinks { diff --git a/src/core/Akka.Persistence.FSharp/Akka.Persistence.FSharp.nuspec b/src/core/Akka.Persistence.FSharp/Akka.Persistence.FSharp.nuspec index 94c2f8c87a2..ad040b1dd86 100644 --- a/src/core/Akka.Persistence.FSharp/Akka.Persistence.FSharp.nuspec +++ b/src/core/Akka.Persistence.FSharp/Akka.Persistence.FSharp.nuspec @@ -9,7 +9,7 @@ F# API for persistence actors in Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj b/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj index 00b1906ddab..bf1e17fe6c8 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj +++ b/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj @@ -34,11 +34,9 @@ - - ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll diff --git a/src/core/Akka.Persistence.TestKit.Tests/LocalSnapshotStoreSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/LocalSnapshotStoreSpec.cs index 4aa34686022..cd2b3a3dee3 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/LocalSnapshotStoreSpec.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/LocalSnapshotStoreSpec.cs @@ -25,7 +25,7 @@ public LocalSnapshotStoreSpec() _path = Sys.Settings.Config.GetString("akka.persistence.snapshot-store.local.dir"); Sys.CreateStorageLocations(_path); - Metadata = WriteSnapshots().ToList(); + Initialize(); } protected override void Dispose(bool disposing) diff --git a/src/core/Akka.Persistence.TestKit.Tests/MemoryJournalSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/MemoryJournalSpec.cs index 4e21f0d0d9e..8fbfb6e2bb6 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/MemoryJournalSpec.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/MemoryJournalSpec.cs @@ -14,7 +14,7 @@ public class MemoryJournalSpec : JournalSpec public MemoryJournalSpec() : base(actorSystemName: "MemoryJournalSpec") { + Initialize(); } } } - diff --git a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj index aa1fb287ff3..2c0293e21a6 100644 --- a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj +++ b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj @@ -35,11 +35,9 @@ - - ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll diff --git a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.nuspec b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.nuspec index 10cd7f83b3e..318102c4b67 100644 --- a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.nuspec +++ b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.nuspec @@ -9,7 +9,7 @@ Testkit for Persistence actor support for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/core/Akka.Persistence.TestKit/Journal/JournalSpec.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalSpec.cs index 59cb651c1cd..9585dd20f74 100644 --- a/src/core/Akka.Persistence.TestKit/Journal/JournalSpec.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/JournalSpec.cs @@ -6,8 +6,8 @@ //----------------------------------------------------------------------- using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; +using System.Linq; using Akka.Actor; using Akka.Configuration; using Akka.TestKit; @@ -33,15 +33,22 @@ class = ""{0}"" }} "; - private readonly TestProbe _senderProbe; - private readonly TestProbe _receiverProbe; + private TestProbe _senderProbe; + private TestProbe _receiverProbe; protected JournalSpec(Config config = null, string actorSystemName = null, string testActorName = null) : base(FromConfig(config).WithFallback(Config), actorSystemName ?? "JournalSpec", testActorName) + { + } + + /// + /// Initializes a journal with set o predefined messages. + /// + protected IEnumerable Initialize() { _senderProbe = CreateTestProbe(); _receiverProbe = CreateTestProbe(); - WriteMessages(1, 5, Pid, _senderProbe.Ref); + return WriteMessages(1, 5, Pid, _senderProbe.Ref); } protected JournalSpec(Type journalType, string actorSystemName = null) @@ -66,7 +73,7 @@ protected bool IsReplayedMessage(ReplayedMessage message, long seqNr, bool isDel && p.SequenceNr == seqNr; } - protected void WriteMessages(int from, int to, string pid, IActorRef sender) + private Persistent[] WriteMessages(int from, int to, string pid, IActorRef sender) { var messages = Enumerable.Range(from, to).Select(i => new Persistent("a-" + i, i, pid, false, sender)).ToArray(); var probe = CreateTestProbe(); @@ -81,6 +88,8 @@ protected void WriteMessages(int from, int to, string pid, IActorRef sender) m.Persistent.Payload.ToString() == ("a-" + n) && m.Persistent.SequenceNr == (long)n && m.Persistent.PersistenceId == Pid); } + + return messages; } [Fact] diff --git a/src/core/Akka.Persistence.TestKit/Snapshot/SnapshotStoreSpec.cs b/src/core/Akka.Persistence.TestKit/Snapshot/SnapshotStoreSpec.cs index e1134ed0eed..9fb351d6a09 100644 --- a/src/core/Akka.Persistence.TestKit/Snapshot/SnapshotStoreSpec.cs +++ b/src/core/Akka.Persistence.TestKit/Snapshot/SnapshotStoreSpec.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Akka.Actor; using Akka.Configuration; using Akka.TestKit; @@ -48,12 +49,20 @@ protected SnapshotStoreSpec(Type snapshotStoreType, string actorSystemName = nul protected IActorRef SnapshotStore { get { return Extension.SnapshotStoreFor(null); } } + /// + /// Initializes a snapshot store with set of predefined snapshots. + /// + protected IEnumerable Initialize() + { + return Metadata = WriteSnapshots().ToList(); + } + private static Config ConfigFromTemplate(Type snapshotStoreType) { return ConfigurationFactory.ParseString(string.Format(_specConfigTemplate, snapshotStoreType.FullName)); } - protected IEnumerable WriteSnapshots() + private IEnumerable WriteSnapshots() { for (int i = 1; i <= 5; i++) { diff --git a/src/core/Akka.Persistence.Tests/Akka.Persistence.Tests.csproj b/src/core/Akka.Persistence.Tests/Akka.Persistence.Tests.csproj index d30d497e06e..2c8c96a2666 100644 --- a/src/core/Akka.Persistence.Tests/Akka.Persistence.Tests.csproj +++ b/src/core/Akka.Persistence.Tests/Akka.Persistence.Tests.csproj @@ -34,11 +34,9 @@ - - ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll diff --git a/src/core/Akka.Persistence.Tests/PersistentActorSpec.Actors.cs b/src/core/Akka.Persistence.Tests/PersistentActorSpec.Actors.cs index cbc373c8886..73babb92e82 100644 --- a/src/core/Akka.Persistence.Tests/PersistentActorSpec.Actors.cs +++ b/src/core/Akka.Persistence.Tests/PersistentActorSpec.Actors.cs @@ -328,8 +328,8 @@ protected override bool ReceiveRecover(object message) protected override bool ReceiveCommand(object message) { - if (CommonBehavior(message)) ; - else if (message is Cmd) HandleCmd(message as Cmd); + if (CommonBehavior(message)) return true; + if (message is Cmd) HandleCmd(message as Cmd); else if (message is SaveSnapshotSuccess) Probe.Tell("saved"); else if (message.ToString() == "snap") SaveSnapshot(Events); else return false; @@ -368,8 +368,8 @@ protected override bool ReceiveRecover(object message) private bool BecomingCommand(object message) { - if (ReceiveCommand(message)) ; - else if (message.ToString() == Message) Probe.Tell(Response); + if (ReceiveCommand(message)) return true; + if (message.ToString() == Message) Probe.Tell(Response); else return false; return true; } diff --git a/src/core/Akka.Persistence.Tests/PersistentActorSpec.cs b/src/core/Akka.Persistence.Tests/PersistentActorSpec.cs index af3fe0dabcb..bc7f700ed46 100644 --- a/src/core/Akka.Persistence.Tests/PersistentActorSpec.cs +++ b/src/core/Akka.Persistence.Tests/PersistentActorSpec.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Threading; using Akka.Actor; -using Akka.Configuration; using Akka.TestKit; using Xunit; diff --git a/src/core/Akka.Persistence/Akka.Persistence.csproj b/src/core/Akka.Persistence/Akka.Persistence.csproj index 85e32a5db00..f20788c1198 100644 --- a/src/core/Akka.Persistence/Akka.Persistence.csproj +++ b/src/core/Akka.Persistence/Akka.Persistence.csproj @@ -42,11 +42,9 @@ - - ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll diff --git a/src/core/Akka.Persistence/Akka.Persistence.nuspec b/src/core/Akka.Persistence/Akka.Persistence.nuspec index b27f586f1c1..7ab2f28a2d2 100644 --- a/src/core/Akka.Persistence/Akka.Persistence.nuspec +++ b/src/core/Akka.Persistence/Akka.Persistence.nuspec @@ -9,7 +9,7 @@ Persistence actor support for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/core/Akka.Persistence/Eventsourced.Lifecycle.cs b/src/core/Akka.Persistence/Eventsourced.Lifecycle.cs index eb9566f535d..eef42f3efad 100644 --- a/src/core/Akka.Persistence/Eventsourced.Lifecycle.cs +++ b/src/core/Akka.Persistence/Eventsourced.Lifecycle.cs @@ -70,22 +70,22 @@ public override void AroundPostStop() protected override void Unhandled(object message) { - if (message is RecoveryCompleted) ; // ignore - else if (message is RecoveryFailure) + if (message is RecoveryCompleted) return; // ignore + if (message is RecoveryFailure) { var msg = string.Format("{0} was killed after recovery failure (persistence id = {1}). To avoid killing persistent actors on recovery failure, a PersistentActor must handle RecoveryFailure messages. Recovery failure was caused by: {2}", GetType().Name, PersistenceId, (message as RecoveryFailure).Cause.Message); throw new ActorKilledException(msg); } - else if (message is PersistenceFailure) + if (message is PersistenceFailure) { var fail = message as PersistenceFailure; var msg = string.Format("{0} was killed after persistence failure (persistence id = {1}, sequence nr: {2}, payload type: {3}). To avoid killing persistent actors on recovery failure, a PersistentActor must handle RecoveryFailure messages. Persistence failure was caused by: {4}", GetType().Name, PersistenceId, fail.SequenceNr, fail.Payload.GetType().Name, fail.Cause.Message); throw new ActorKilledException(msg); } - else base.Unhandled(message); + base.Unhandled(message); } } } diff --git a/src/core/Akka.Persistence/Eventsourced.Recovery.cs b/src/core/Akka.Persistence/Eventsourced.Recovery.cs index 8d627f032db..7a710c91626 100644 --- a/src/core/Akka.Persistence/Eventsourced.Recovery.cs +++ b/src/core/Akka.Persistence/Eventsourced.Recovery.cs @@ -165,7 +165,7 @@ private EventsourcedState ReplayFailed(Exception cause, object failureMessage) } else if (message is ReplayMessagesSuccess) ReplayCompleted(cause, failureMessage); else if (message is ReplayedMessage) UpdateLastSequenceNr(((ReplayedMessage)message).Persistent); - else if (message is Recover) ; // ignore + else if (message is Recover) return; // ignore else _internalStash.Stash(); }); } diff --git a/src/core/Akka.Persistence/Journal/SyncWriteJournal.cs b/src/core/Akka.Persistence/Journal/SyncWriteJournal.cs index 24a0d1c6480..33f33a341fa 100644 --- a/src/core/Akka.Persistence/Journal/SyncWriteJournal.cs +++ b/src/core/Akka.Persistence/Journal/SyncWriteJournal.cs @@ -63,7 +63,7 @@ private void HandleDeleteMessagesTo(DeleteMessagesTo msg) if (CanPublish) Context.System.EventStream.Publish(msg); } - catch (Exception e) { /* do nothing */ } + catch (Exception) { /* do nothing */ } } private void HandleReadHighestSequenceNr(ReadHighestSequenceNr msg) @@ -77,9 +77,10 @@ private void HandleReadHighestSequenceNr(ReadHighestSequenceNr msg) private void HandleReplayMessages(ReplayMessages msg) { + var sender = Sender; ReplayMessagesAsync(msg.PersistenceId, msg.FromSequenceNr, msg.ToSequenceNr, msg.Max, persistent => { - if (!persistent.IsDeleted || msg.ReplayDeleted) msg.PersistentActor.Tell(new ReplayedMessage(persistent), persistent.Sender); + if (!persistent.IsDeleted || msg.ReplayDeleted) msg.PersistentActor.Tell(new ReplayedMessage(persistent), sender); }) .NotifyAboutReplayCompletion(msg.PersistentActor) .ContinueWith(t => diff --git a/src/core/Akka.Persistence/PersistentView.Lifecycle.cs b/src/core/Akka.Persistence/PersistentView.Lifecycle.cs index 9d79b565d8d..d2a25ae39e5 100644 --- a/src/core/Akka.Persistence/PersistentView.Lifecycle.cs +++ b/src/core/Akka.Persistence/PersistentView.Lifecycle.cs @@ -54,15 +54,15 @@ protected override bool AroundReceive(Receive receive, object message) protected override void Unhandled(object message) { - if (message is RecoveryCompleted) ; // ignore - else if (message is RecoveryFailure) + if (message is RecoveryCompleted) return; // ignore + if (message is RecoveryFailure) { var fail = (RecoveryFailure)message; var errorMessage = string.Format("Persistent view killed after the recovery failure (Persistence id: {0}). To avoid killing persistent actors on recovery failures, PersistentView must handle RecoveryFailure messages. Failure was caused by: {1}", PersistenceId, fail.Cause.Message); throw new ActorKilledException(errorMessage); } - else base.Unhandled(message); + base.Unhandled(message); } } } diff --git a/src/core/Akka.Persistence/PersistentView.Recovery.cs b/src/core/Akka.Persistence/PersistentView.Recovery.cs index db722eb541a..36bc0d5fbf9 100644 --- a/src/core/Akka.Persistence/PersistentView.Recovery.cs +++ b/src/core/Akka.Persistence/PersistentView.Recovery.cs @@ -65,8 +65,8 @@ private ViewState RecoveryStarted(long replayMax) { return new ViewState("recovery started - replayMax: " + replayMax, true, (receive, message) => { - if (message is Recover) ; // ignore - else if (message is LoadSnapshotResult) + if (message is Recover) return; // ignore + if (message is LoadSnapshotResult) { var loadResult = (LoadSnapshotResult)message; if (loadResult.Snapshot != null) @@ -110,7 +110,7 @@ private ViewState ReplayStarted(bool shouldAwait) _internalStash.Stash(); } } - else if (message is Recover) ; // ignore + else if (message is Recover) return; // ignore else if (message is ReplayedMessage) { var replayedMessage = (ReplayedMessage)message; @@ -172,7 +172,7 @@ private ViewState ReplayFailed(Exception cause, object failedMessage) } else if (message is ReplayMessagesSuccess) OnReplayFailureCompleted(receive, cause, failedMessage as IPersistentRepresentation); else if (message is ReplayedMessage) UpdateLastSequenceNr(((ReplayedMessage)message).Persistent); - else if (message is Recover) ; // ignore + else if (message is Recover) return; // ignore else _internalStash.Stash(); }); } @@ -204,7 +204,7 @@ private ViewState Idle() var scheduled = (ScheduledUpdate) message; ChangeStateToReplayStarted(false, scheduled.ReplayMax); } - else if (message is Recover) ; // ignore + else if (message is Recover) return; // ignore else base.AroundReceive(receive, message); }); } diff --git a/src/core/Akka.Remote.TestKit/Akka.Remote.TestKit.csproj b/src/core/Akka.Remote.TestKit/Akka.Remote.TestKit.csproj index fec94b62135..e2038c39a8a 100644 --- a/src/core/Akka.Remote.TestKit/Akka.Remote.TestKit.csproj +++ b/src/core/Akka.Remote.TestKit/Akka.Remote.TestKit.csproj @@ -41,7 +41,6 @@ ..\..\packages\Microsoft.Bcl.Immutable.1.0.34\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll diff --git a/src/core/Akka.Remote.TestKit/CommandLine.cs b/src/core/Akka.Remote.TestKit/CommandLine.cs index 3349c8166ac..d785da35fa7 100644 --- a/src/core/Akka.Remote.TestKit/CommandLine.cs +++ b/src/core/Akka.Remote.TestKit/CommandLine.cs @@ -14,7 +14,7 @@ namespace Akka.Remote.TestKit /// /// Command line argument parser for individual node tests during a . /// - /// Parses arguments from using the same conventions as cannonical Akka. + /// Parses arguments from using the same conventions as canonical Akka. /// /// For example (from the Akka.NodeTestRunner source): /// diff --git a/src/core/Akka.Remote.TestKit/MultiNodeSpec.cs b/src/core/Akka.Remote.TestKit/MultiNodeSpec.cs index b859eef29f1..a2e0fb69780 100644 --- a/src/core/Akka.Remote.TestKit/MultiNodeSpec.cs +++ b/src/core/Akka.Remote.TestKit/MultiNodeSpec.cs @@ -18,7 +18,6 @@ using Akka.Event; using Akka.TestKit; using Akka.TestKit.Xunit; -using Akka.Util; using Akka.Util.Internal; using Helios.Topology; diff --git a/src/core/Akka.Remote.Tests/AccrualFailureDetectorSpec.cs b/src/core/Akka.Remote.Tests/AccrualFailureDetectorSpec.cs index 32c4a72249d..8cbe461dab9 100644 --- a/src/core/Akka.Remote.Tests/AccrualFailureDetectorSpec.cs +++ b/src/core/Akka.Remote.Tests/AccrualFailureDetectorSpec.cs @@ -68,7 +68,7 @@ public void AccrualFailureDetector_must_return_realistic_phi_values() ShouldBe(fd.Phi(kv.Key, 1000.0, 100.0), kv.Value, 0.1); } - //larger stdDeviation reuslts => lower phi + //larger stdDeviation results => lower phi Assert.True(fd.Phi(1100, 1000.0, 500.0) < fd.Phi(1100, 1000.0, 100.0)); } diff --git a/src/core/Akka.Remote.Tests/AckedDeliverySpec.cs b/src/core/Akka.Remote.Tests/AckedDeliverySpec.cs index 97da17069e0..9be731fdd12 100644 --- a/src/core/Akka.Remote.Tests/AckedDeliverySpec.cs +++ b/src/core/Akka.Remote.Tests/AckedDeliverySpec.cs @@ -381,7 +381,7 @@ public void SendBuffer_and_ReceiveBuffer_must_correctly_cooperate_with_each_othe global::System.Diagnostics.Debug.WriteLine("Successfully delivered {0} messages from {1}", received.Count, msgCount); global::System.Diagnostics.Debug.WriteLine("Entering reliable phase"); - //Finalizing pahase + //Finalizing phase for (var i = 1; i <= msgCount; i++) { senderSteps(1, 1.0); diff --git a/src/core/Akka.Remote.Tests/FailureDetectorRegistrySpec.cs b/src/core/Akka.Remote.Tests/FailureDetectorRegistrySpec.cs index d89b58a6287..9dd68b5b120 100644 --- a/src/core/Akka.Remote.Tests/FailureDetectorRegistrySpec.cs +++ b/src/core/Akka.Remote.Tests/FailureDetectorRegistrySpec.cs @@ -41,7 +41,7 @@ public void FailureDetectorRegistry_must_mark_node_as_dead_if_heartbeats_are_mis fd.Heartbeat("resource1"); //1000 fd.Heartbeat("resource1"); //1100 Assert.True(fd.IsAvailable("resource1")); //1200 - fd.Heartbeat("resource2"); //5200, but unrelated resouce + fd.Heartbeat("resource2"); //5200, but unrelated resource Assert.False(fd.IsAvailable("resource1")); } diff --git a/src/core/Akka.Remote.Tests/RemotingSpec.cs b/src/core/Akka.Remote.Tests/RemotingSpec.cs index 7c65995773e..db98979edf7 100644 --- a/src/core/Akka.Remote.Tests/RemotingSpec.cs +++ b/src/core/Akka.Remote.Tests/RemotingSpec.cs @@ -6,10 +6,12 @@ //----------------------------------------------------------------------- using System; +using System.Linq; using System.Threading.Tasks; using Akka.Actor; using Akka.Configuration; using Akka.Remote.Transport; +using Akka.Routing; using Akka.TestKit; using Akka.Util; using Akka.Util.Internal; @@ -198,6 +200,36 @@ public void Remoting_must_create_by_IndirectActorProducer_and_ping() } } + [Fact] + public async Task Bug_884_Remoting_must_support_reply_to_Routee() + { + var router = Sys.ActorOf(new RoundRobinPool(3).Props(Props.Create(() => new Reporter(TestActor)))); + var routees = await router.Ask(new GetRoutees()); + + //have one of the routees send the message + var targetRoutee = routees.Members.Cast().Select(x => x.Actor).First(); + here.Tell("ping", targetRoutee); + var msg = ExpectMsg>(TimeSpan.FromSeconds(1.5)); + Assert.Equal("pong", msg.Item1); + Assert.Equal(targetRoutee, msg.Item2); + } + + [Fact] + public async Task Bug_884_Remoting_must_support_reply_to_child_of_Routee() + { + var props = Props.Create(() => new Reporter(TestActor)); + var router = Sys.ActorOf(new RoundRobinPool(3).Props(Props.Create(() => new NestedDeployer(props)))); + var routees = await router.Ask(new GetRoutees()); + + //have one of the routees send the message + var targetRoutee = routees.Members.Cast().Select(x => x.Actor).First(); + var reporter = await targetRoutee.Ask(new NestedDeployer.GetNestedReporter()); + here.Tell("ping", reporter); + var msg = ExpectMsg>(TimeSpan.FromSeconds(1.5)); + Assert.Equal("pong", msg.Item1); + Assert.Equal(reporter, msg.Item2); + } + #endregion #region Internal Methods @@ -305,6 +337,52 @@ public ActorSelReq(string s) public string S { get; private set; } } + class Reporter : UntypedActor + { + private IActorRef _reportTarget; + + public Reporter(IActorRef reportTarget) + { + _reportTarget = reportTarget; + } + + + protected override void OnReceive(object message) + { + _reportTarget.Forward(message); + } + } + + class NestedDeployer : UntypedActor + { + private Props _reporterProps; + private IActorRef _repoterActorRef; + + public class GetNestedReporter { } + + public NestedDeployer(Props reporterProps) + { + _reporterProps = reporterProps; + } + + protected override void PreStart() + { + _repoterActorRef = Context.ActorOf(_reporterProps); + } + + protected override void OnReceive(object message) + { + if (message is GetNestedReporter) + { + Sender.Tell(_repoterActorRef); + } + else + { + Unhandled(message); + } + } + } + class Echo1 : UntypedActor { private IActorRef target = Context.System.DeadLetters; diff --git a/src/core/Akka.Remote/Akka.Remote.nuspec b/src/core/Akka.Remote/Akka.Remote.nuspec index 5d55f26383a..dca890338ce 100644 --- a/src/core/Akka.Remote/Akka.Remote.nuspec +++ b/src/core/Akka.Remote/Akka.Remote.nuspec @@ -9,7 +9,7 @@ Remote actor support for Akka.NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/core/Akka.Remote/DeadlineFailureDetector.cs b/src/core/Akka.Remote/DeadlineFailureDetector.cs index 4d8a7028208..87c5fbbb7fd 100644 --- a/src/core/Akka.Remote/DeadlineFailureDetector.cs +++ b/src/core/Akka.Remote/DeadlineFailureDetector.cs @@ -9,7 +9,6 @@ using System.Threading; using Akka.Configuration; using Akka.Event; -using Akka.Util; namespace Akka.Remote { diff --git a/src/core/Akka.Remote/DefaultFailureDetectorRegistry.cs b/src/core/Akka.Remote/DefaultFailureDetectorRegistry.cs index f795dee1729..f72ee92d7fd 100644 --- a/src/core/Akka.Remote/DefaultFailureDetectorRegistry.cs +++ b/src/core/Akka.Remote/DefaultFailureDetectorRegistry.cs @@ -65,7 +65,7 @@ public void Heartbeat(T resource) //First one wins and creates the new FailureDetector lock (_failureDetectorCreationLock) { - // First check for non-existing key wa outside the lock, and a second thread might just have released thelock + // First check for non-existing key wa outside the lock, and a second thread might just have released the lock // when this one acquired it, so the second check is needed (double-check locking pattern) var oldTable = new Dictionary(ResourceToFailureDetector); if (oldTable.ContainsKey(resource)) diff --git a/src/core/Akka.Remote/Endpoint.cs b/src/core/Akka.Remote/Endpoint.cs index 7fd90928ef4..ed36fc9ebbc 100644 --- a/src/core/Akka.Remote/Endpoint.cs +++ b/src/core/Akka.Remote/Endpoint.cs @@ -991,7 +991,7 @@ protected override void Unhandled(object message) } else { - // initalizing, buffer and take care of it later when buffer is sent + // initializing, buffer and take care of it later when buffer is sent EnqueueInBuffer(message); } } @@ -1169,11 +1169,11 @@ private void BecomeWritingOrSendBufferedMessages() { if (!_buffer.Any()) { - Context.Become(Writing, true); + Context.Become(Writing); } else { - Context.Become(Buffering, true); + Context.Become(Buffering); SendBufferedMessages(); } } @@ -1353,6 +1353,7 @@ public sealed class TakeOver : INoSerializationVerificationNeeded /// Create a new TakeOver command /// /// The handle of the new association + /// public TakeOver(AkkaProtocolHandle protocolHandle, IActorRef replyTo) { ProtocolHandle = protocolHandle; diff --git a/src/core/Akka.Remote/RemoteWatcher.cs b/src/core/Akka.Remote/RemoteWatcher.cs index fc3571a387e..d3110f3bf57 100644 --- a/src/core/Akka.Remote/RemoteWatcher.cs +++ b/src/core/Akka.Remote/RemoteWatcher.cs @@ -25,7 +25,7 @@ namespace Akka.Remote /// For a new node to be watched this actor periodically sends /// to the peer actor on the other node, which replies with /// message back. The failure detector on the watching side monitors these heartbeat messages. - /// If arrival of hearbeat messages stops it will be detected and this actor will publish + /// If arrival of heartbeat messages stops it will be detected and this actor will publish /// to the . /// /// When all actors on a node have been unwatched it will stop sending heartbeat messages. diff --git a/src/core/Akka.Remote/SystemNanoTime.cs b/src/core/Akka.Remote/SystemNanoTime.cs index 50e148f88a7..7a5329c8a49 100644 --- a/src/core/Akka.Remote/SystemNanoTime.cs +++ b/src/core/Akka.Remote/SystemNanoTime.cs @@ -28,7 +28,7 @@ static SystemNanoTime() public static long GetNanos() { - return StopWatch.ElapsedTicks.ToNanos(); + return StopWatch.Elapsed.Ticks.ToNanos(); } internal const long NanosPerTick = 100; diff --git a/src/core/Akka.Remote/Transport/AkkaProtocolTransport.cs b/src/core/Akka.Remote/Transport/AkkaProtocolTransport.cs index 4f5a8ee9b21..38143de0cc4 100644 --- a/src/core/Akka.Remote/Transport/AkkaProtocolTransport.cs +++ b/src/core/Akka.Remote/Transport/AkkaProtocolTransport.cs @@ -157,7 +157,7 @@ protected override void Ready(object message) new AkkaPduProtobuffCodec(), failureDetector)), ActorNameFor(handle.RemoteAddress)); }) - .With(au => CreateOutboundStateActor(au.RemoteAddress, au.StatusPromise, null)) //need to create an Outbond ProtocolStateActor + .With(au => CreateOutboundStateActor(au.RemoteAddress, au.StatusPromise, null)) //need to create an Outbound ProtocolStateActor .With(au => CreateOutboundStateActor(au.RemoteAddress, au.StatusCompletionSource, au.RefuseUid)); } diff --git a/src/core/Akka.Remote/Transport/FailureInjectorTransportAdapter.cs b/src/core/Akka.Remote/Transport/FailureInjectorTransportAdapter.cs index 36941293f3e..e06b05e7059 100644 --- a/src/core/Akka.Remote/Transport/FailureInjectorTransportAdapter.cs +++ b/src/core/Akka.Remote/Transport/FailureInjectorTransportAdapter.cs @@ -38,7 +38,7 @@ public FailureInjectorException(string msg) Msg = msg; } - protected FailureInjectorException(SerializationInfo info, StreamingContext context) + private FailureInjectorException(SerializationInfo info, StreamingContext context) : base(info, context) { } diff --git a/src/core/Akka.Remote/Transport/TestTransport.cs b/src/core/Akka.Remote/Transport/TestTransport.cs index caecffee8be..5bf20c0b5f1 100644 --- a/src/core/Akka.Remote/Transport/TestTransport.cs +++ b/src/core/Akka.Remote/Transport/TestTransport.cs @@ -296,7 +296,7 @@ public DisassociateAttempt(Address requestor, Address remote) /// control to the timing of completion of the associated Task. /// /// The utility is implemented as a stack of behaviors, where the behavior on the top of the stack represents the - /// currently active behavior. The bottom of the stack alway contains the which + /// currently active behavior. The bottom of the stack always contains the which /// can not be popped out. /// public class SwitchableLoggedBehavior diff --git a/src/core/Akka.TestKit.Tests/TestEventListenerTests/EventFilterTestBase.cs b/src/core/Akka.TestKit.Tests/TestEventListenerTests/EventFilterTestBase.cs index 506dab6e97b..af83b4c4329 100644 --- a/src/core/Akka.TestKit.Tests/TestEventListenerTests/EventFilterTestBase.cs +++ b/src/core/Akka.TestKit.Tests/TestEventListenerTests/EventFilterTestBase.cs @@ -25,7 +25,7 @@ protected EventFilterTestBase(string config) // ReSharper disable once DoNotCallOverridableMethodsInConstructor SendRawLogEventMessage(initLoggerMessage); ExpectMsg("OK"); - //From now on we know that all messsages will be forwarded to TestActor + //From now on we know that all messages will be forwarded to TestActor } protected abstract void SendRawLogEventMessage(object message); diff --git a/src/core/Akka.TestKit/ActorCellKeepingSynchronizationContext.cs b/src/core/Akka.TestKit/ActorCellKeepingSynchronizationContext.cs new file mode 100644 index 00000000000..aa6da33439a --- /dev/null +++ b/src/core/Akka.TestKit/ActorCellKeepingSynchronizationContext.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.Actor.Internal; + +namespace Akka.TestKit +{ + class ActorCellKeepingSynchronizationContext : SynchronizationContext + { + private readonly ActorCell _cell; + + public ActorCellKeepingSynchronizationContext(ActorCell cell) + { + _cell = cell; + } + + + public override void Post(SendOrPostCallback d, object state) + { + ThreadPool.UnsafeQueueUserWorkItem(_ => + { + var oldCell = InternalCurrentActorCellKeeper.Current; + var oldContext = Current; + SetSynchronizationContext(this); + InternalCurrentActorCellKeeper.Current = _cell; + + try + { + d(state); + } + finally + { + InternalCurrentActorCellKeeper.Current = oldCell; + SetSynchronizationContext(oldContext); + } + }, state); + } + + public override void Send(SendOrPostCallback d, object state) + { + var tcs = new TaskCompletionSource(); + Post(_ => + { + try + { + d(state); + tcs.SetResult(0); + } + catch (Exception e) + { + tcs.TrySetException(e); + } + }, state); + tcs.Task.Wait(); + } + } +} + \ No newline at end of file diff --git a/src/core/Akka.TestKit/Akka.TestKit.csproj b/src/core/Akka.TestKit/Akka.TestKit.csproj index d51694417be..b1359535c0e 100644 --- a/src/core/Akka.TestKit/Akka.TestKit.csproj +++ b/src/core/Akka.TestKit/Akka.TestKit.csproj @@ -68,6 +68,7 @@ Properties\SharedAssemblyInfo.cs + diff --git a/src/core/Akka.TestKit/Akka.TestKit.nuspec b/src/core/Akka.TestKit/Akka.TestKit.nuspec index d402c39b395..e645cf1dc6d 100644 --- a/src/core/Akka.TestKit/Akka.TestKit.nuspec +++ b/src/core/Akka.TestKit/Akka.TestKit.nuspec @@ -10,7 +10,7 @@ This only contains base functionality. You need a Akka.TestKit.* package! https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -18,4 +18,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/core/Akka.TestKit/EventFilter/EventFilterFactory.cs b/src/core/Akka.TestKit/EventFilter/EventFilterFactory.cs index 796b878d234..95b85774be8 100644 --- a/src/core/Akka.TestKit/EventFilter/EventFilterFactory.cs +++ b/src/core/Akka.TestKit/EventFilter/EventFilterFactory.cs @@ -61,7 +61,7 @@ public IEventFilterApplier Exception(Regex pattern, string source = /// The type of the exception. It must be a . /// The event must match the pattern to be filtered. /// >Optional. The event source. - /// Optional. When set to true not only the top level exception is matched, but inner exceptions are also checked until one macthes. Default: false + /// Optional. When set to true not only the top level exception is matched, but inner exceptions are also checked until one matches. Default: false /// The new filter public IEventFilterApplier Exception(Type exceptionType, Regex pattern, string source = null, bool checkInnerExceptions=false) { @@ -118,7 +118,7 @@ public IEventFilterApplier Exception(string message = null, string s /// Optional. If specified (and neither nor are specified), the event must contain the string to be filtered. /// >Optional. If specified (and is not specified, the event must start with the string to be filtered. /// >Optional. The event source. - /// Optional. When set to true not only the top level exception is matched, but inner exceptions are also checked until one macthes. Default: false + /// Optional. When set to true not only the top level exception is matched, but inner exceptions are also checked until one matches. Default: false /// The new filter public IEventFilterApplier Exception(Type exceptionType, string message = null, string start = null, string contains = null, string source = null, bool checkInnerExceptions=false) { diff --git a/src/core/Akka.TestKit/TestKitBase.cs b/src/core/Akka.TestKit/TestKitBase.cs index e9a2d16bfde..cce55da34d5 100644 --- a/src/core/Akka.TestKit/TestKitBase.cs +++ b/src/core/Akka.TestKit/TestKitBase.cs @@ -110,6 +110,8 @@ private TestKitBase(ITestKitAssertions assertions, ActorSystem system, Config co { InternalCurrentActorCellKeeper.Current = (ActorCell)((ActorRefWithCell)testActor).Underlying; } + SynchronizationContext.SetSynchronizationContext( + new ActorCellKeepingSynchronizationContext(InternalCurrentActorCellKeeper.Current)); _testActor = testActor; } diff --git a/src/core/Akka.Tests.Shared.Internals/Akka.Tests.Shared.Internals.csproj b/src/core/Akka.Tests.Shared.Internals/Akka.Tests.Shared.Internals.csproj index 3f4c09cd9de..45155f915e9 100644 --- a/src/core/Akka.Tests.Shared.Internals/Akka.Tests.Shared.Internals.csproj +++ b/src/core/Akka.Tests.Shared.Internals/Akka.Tests.Shared.Internals.csproj @@ -34,11 +34,7 @@ - - - - ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll diff --git a/src/core/Akka.Tests.Shared.Internals/AkkaSpec.cs b/src/core/Akka.Tests.Shared.Internals/AkkaSpec.cs index b98e934a0fe..57dcaf804c8 100644 --- a/src/core/Akka.Tests.Shared.Internals/AkkaSpec.cs +++ b/src/core/Akka.Tests.Shared.Internals/AkkaSpec.cs @@ -120,7 +120,7 @@ protected void Intercept(Action actionThatThrows) { try { - actionThatThrows(); + actionThatThrows(); } catch(Exception) { diff --git a/src/core/Akka.Tests/Actor/ActorDslSpec.cs b/src/core/Akka.Tests/Actor/ActorDslSpec.cs index b6b86a61d10..3cca2b96b4a 100644 --- a/src/core/Akka.Tests/Actor/ActorDslSpec.cs +++ b/src/core/Akka.Tests/Actor/ActorDslSpec.cs @@ -30,17 +30,23 @@ public void A_lightweight_creator_must_support_become_stacked() { var a = Sys.ActorOf(c => c.Become((msg, ctx) => { - if (msg == "info") + var message = msg as string; + if (message == null) return; + + if (message == "info") TestActor.Tell("A"); - else if (msg == "switch") + else if (message == "switch") c.BecomeStacked((msg2, ctx2) => { - if (msg2 == "info") + var message2 = msg2 as string; + if (message2 == null) return; + + if (message2 == "info") TestActor.Tell("B"); - else if (msg2 == "switch") + else if (message2 == "switch") c.UnbecomeStacked(); }); - else if (msg == "lobotomize") + else if (message == "lobotomize") c.UnbecomeStacked(); })); diff --git a/src/core/Akka.Tests/Actor/InboxSpec.cs b/src/core/Akka.Tests/Actor/InboxSpec.cs index aa9f47b7709..bd7ee5e5e3e 100644 --- a/src/core/Akka.Tests/Actor/InboxSpec.cs +++ b/src/core/Akka.Tests/Actor/InboxSpec.cs @@ -19,7 +19,8 @@ namespace Akka.Tests.Actor { public class InboxSpec : AkkaSpec { - private Inbox _inbox; + private readonly Inbox _inbox; + public InboxSpec() : base("akka.actor.inbox.inbox-size=1000") //Default is 1000 but just to make sure these tests don't fail we set it { @@ -105,7 +106,7 @@ public void Inbox_have_maximum_queue_size() o.ShouldBe(0); } - //The inbox should be empty now, so receiving should result in a timeout + //The inbox should be empty now, so receiving should result in a timeout Intercept(() => { var received = _inbox.Receive(TimeSpan.FromSeconds(1)); @@ -118,15 +119,15 @@ public void Inbox_have_maximum_queue_size() } } - [Fact] public void Inbox_have_a_default_and_custom_timeouts() { - Within(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(6), () => + Within(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(6), () => { Intercept(() => _inbox.Receive()); return true; }); + Within(TimeSpan.FromSeconds(1), () => { Intercept(() => _inbox.Receive(TimeSpan.FromMilliseconds(100))); diff --git a/src/core/Akka.Tests/Actor/LocalActorRefProviderSpec.cs b/src/core/Akka.Tests/Actor/LocalActorRefProviderSpec.cs index 0af2a27aea4..b2f594866dc 100644 --- a/src/core/Akka.Tests/Actor/LocalActorRefProviderSpec.cs +++ b/src/core/Akka.Tests/Actor/LocalActorRefProviderSpec.cs @@ -96,7 +96,7 @@ private class ActorWithDuplicateChild : ActorBase { protected override bool Receive(object message) { - if (message == "") + if (message as string == "") { var a = Context.ActorOf(Props.Empty, "duplicate"); var b = Context.ActorOf(Props.Empty, "duplicate"); @@ -117,7 +117,7 @@ public ParentActor() protected override bool Receive(object message) { - if (message == "GetChild") + if (message as string == "GetChild") { Sender.Tell(this.childActorRef); return true; diff --git a/src/core/Akka.Tests/Actor/RootGuardianActorRef_Tests.cs b/src/core/Akka.Tests/Actor/RootGuardianActorRef_Tests.cs index c09b688366e..57e554473ae 100644 --- a/src/core/Akka.Tests/Actor/RootGuardianActorRef_Tests.cs +++ b/src/core/Akka.Tests/Actor/RootGuardianActorRef_Tests.cs @@ -5,7 +5,6 @@ // //----------------------------------------------------------------------- -using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Akka.Actor; diff --git a/src/core/Akka.Tests/Actor/Stash/ActorWithStashSpec.cs b/src/core/Akka.Tests/Actor/Stash/ActorWithStashSpec.cs index 584b13717d1..49658389780 100644 --- a/src/core/Akka.Tests/Actor/Stash/ActorWithStashSpec.cs +++ b/src/core/Akka.Tests/Actor/Stash/ActorWithStashSpec.cs @@ -193,7 +193,7 @@ public StashingTwiceActor() { Stash.Stash(); } - catch(IllegalActorStateException e) + catch(IllegalActorStateException) { _state.ExpectedException.Open(); } diff --git a/src/core/Akka.Tests/Actor/SupervisorHierarchySpec.cs b/src/core/Akka.Tests/Actor/SupervisorHierarchySpec.cs index fecd712189c..cc05455125d 100644 --- a/src/core/Akka.Tests/Actor/SupervisorHierarchySpec.cs +++ b/src/core/Akka.Tests/Actor/SupervisorHierarchySpec.cs @@ -137,7 +137,7 @@ public void A_supervisor_must_send_notifications_to_supervisor_when_permanent_fa ctx.Watch(crasher); }, "boss"); - //We have built this hiearchy: + //We have built this hierarchy: // boss // | // crasher @@ -146,7 +146,7 @@ public void A_supervisor_must_send_notifications_to_supervisor_when_permanent_fa //Crasher will be restarted, and during PostRestart countDownMessages will count down. //We then send another "killCrasher", which again will send Kill to crasher. It crashes, //decider says it should be restarted but since we specified maximum 1 restart/5seconds it will be - //permantely stopped. Boss, which watches crasher, recieves Terminated, and counts down countDownMax + //permanently stopped. Boss, which watches crasher, receives Terminated, and counts down countDownMax EventFilter.Exception().Expect(2, () => { boss.Tell("killCrasher"); @@ -159,7 +159,7 @@ public void A_supervisor_must_send_notifications_to_supervisor_when_permanent_fa [Fact] public void A_supervisor_hierarchy_must_resume_children_after_Resume() { - //Build this hiearchy: + //Build this hierarchy: // boss // | // middle @@ -199,7 +199,7 @@ public void A_supervisor_hierarchy_must_suspend_children_while_failing() c.Receive("spawn", (s, ctx) => ctx.Sender.Tell(ctx.ActorOf())); }, "slowResumer"); - //Build this hiearchy: + //Build this hierarchy: // slowResumer // | // boss @@ -263,7 +263,7 @@ public void A_supervisor_hierarchy_must_handle_failure_in_creation_when_supervis { var ca = createAttempt.IncrementAndGet(); if (ca <= 6 && ca % 3 == 0) - childContext.ActorOf(BlackHoleActor.Props, "workingChild"); + childContext.ActorOf(BlackHoleActor.Props, "workingChild" + ca); if (ca < 6) throw new InvalidOperationException("OH NO!"); childDsl.OnPreStart = _ => preStartCalled.IncrementAndGet(); diff --git a/src/core/Akka.Tests/Actor/SystemGuardianTests.cs b/src/core/Akka.Tests/Actor/SystemGuardianTests.cs index 8f70ad7f1c5..7ad7f119d4e 100644 --- a/src/core/Akka.Tests/Actor/SystemGuardianTests.cs +++ b/src/core/Akka.Tests/Actor/SystemGuardianTests.cs @@ -5,11 +5,9 @@ // //----------------------------------------------------------------------- -using System.CodeDom; using Akka.Actor; using Akka.Dispatch.SysMsg; using Akka.TestKit; -using Akka.TestKit.TestActors; using Xunit; namespace Akka.Tests.Actor diff --git a/src/core/Akka.Tests/Akka.Tests.csproj b/src/core/Akka.Tests/Akka.Tests.csproj index 23bd4d7f766..401cb67aa55 100644 --- a/src/core/Akka.Tests/Akka.Tests.csproj +++ b/src/core/Akka.Tests/Akka.Tests.csproj @@ -120,6 +120,7 @@ + diff --git a/src/core/Akka.Tests/Dispatch/AsyncAwaitSpec.cs b/src/core/Akka.Tests/Dispatch/AsyncAwaitSpec.cs index 203869c327d..eb14022b53c 100644 --- a/src/core/Akka.Tests/Dispatch/AsyncAwaitSpec.cs +++ b/src/core/Akka.Tests/Dispatch/AsyncAwaitSpec.cs @@ -9,11 +9,30 @@ using System.Threading.Tasks; using Akka.Actor; using Akka.Dispatch; +using Akka.Event; using Akka.TestKit; using Xunit; namespace Akka.Tests.Dispatch { + class AsyncActor : ReceiveActor + { + public AsyncActor() + { + Receive( async s => + { + await Task.Yield(); + await Task.Delay(TimeSpan.FromMilliseconds(100)); + if (s == "stop") + { + Sender.Tell("done"); + } + }); + } + + ILoggingAdapter Log = Context.GetLogger(); + } + public class SuspendActor : ReceiveActor { public SuspendActor() @@ -313,9 +332,23 @@ public async Task Actors_should_be_able_to_reenter() public async Task Actors_should_be_able_to_suspend_reentrancy() { var asker = Sys.ActorOf(Props.Create(() => new SuspendActor())); - var res = await asker.Ask("start", TimeSpan.FromSeconds(555)); + var res = await asker.Ask("start", TimeSpan.FromSeconds(5)); res.ShouldBe(0); } + + [Fact] + public async Task Actor_should_be_able_to_resume_suspend() + { + var asker = Sys.ActorOf(); + + for (var i = 0; i < 10; i++) + { + asker.Tell("msg #" + i); + } + + var res = await asker.Ask("stop", TimeSpan.FromSeconds(5)); + res.ShouldBe("done"); + } } } diff --git a/src/core/Akka.Tests/Dispatch/DispatchersSpec.cs b/src/core/Akka.Tests/Dispatch/DispatchersSpec.cs index 3ca9f10a8b8..4f9e471f7ec 100644 --- a/src/core/Akka.Tests/Dispatch/DispatchersSpec.cs +++ b/src/core/Akka.Tests/Dispatch/DispatchersSpec.cs @@ -5,13 +5,7 @@ // //----------------------------------------------------------------------- -/** - * Copyright (C) 2009-2015 Typesafe Inc. - * Original C# code written by Akka.NET project - */ - using System; -using System.Threading; using Akka.Actor; using Akka.Configuration; using Akka.Dispatch; @@ -47,7 +41,7 @@ public static Config DispatcherConfiguration } my-synchronized-dispather{ type = SynchronizedDispatcher - throughput = 10 + throughput = 10 } } akka.actor.deployment{ @@ -172,6 +166,15 @@ public void Dispatchers_must_return_separate_instances_of_dispatchers_with_diffe d1.ShouldNotBeSame(d3); } + + [Fact] + public void PinnedDispatchers_must_return_new_instance_each_time() + { + var d1 = Lookup("myapp.my-pinned-dispatcher"); + var d2 = Lookup("myapp.my-pinned-dispatcher"); + d1.ShouldNotBeSame(d2); + } + #endregion #region Support methods and classes diff --git a/src/core/Akka.Tests/Dispatch/MailboxesSpec.cs b/src/core/Akka.Tests/Dispatch/MailboxesSpec.cs index 831c3a1de1c..0cddf909080 100644 --- a/src/core/Akka.Tests/Dispatch/MailboxesSpec.cs +++ b/src/core/Akka.Tests/Dispatch/MailboxesSpec.cs @@ -6,6 +6,7 @@ //----------------------------------------------------------------------- using System; +using System.Linq; using Akka.Actor; using Akka.Dispatch; using Akka.Dispatch.SysMsg; @@ -32,6 +33,14 @@ protected override int PriorityGenerator(object message) } } + public class IntPriorityMailbox : UnboundedPriorityMailbox + { + protected override int PriorityGenerator(object message) + { + return message as int? ?? Int32.MaxValue; + } + } + public class MailboxesSpec : AkkaSpec { public MailboxesSpec() : base(GetConfig()) @@ -45,6 +54,10 @@ private static string GetConfig() string-prio-mailbox { mailbox-type : """ + typeof(TestPriorityMailbox).AssemblyQualifiedName + @""" } + +int-prio-mailbox { + mailbox-type : """ + typeof(IntPriorityMailbox).AssemblyQualifiedName + @""" +} "; } @@ -82,6 +95,43 @@ public void CanUseUnboundedPriorityMailbox() ExpectNoMsg(TimeSpan.FromSeconds(0.3)); } + + [Fact] + public void PriorityMailboxKeepsOrderingWithManyPriorityValues() + { + var actor = Sys.ActorOf(EchoActor.Props(this).WithMailbox("int-prio-mailbox"), "echo"); + + //pause mailbox until all messages have been told + actor.Tell(Suspend.Instance); + + // creates 50 messages with values spanning from Int32.MinValue to Int32.MaxValue + var values = new int[50]; + var increment = (int)(UInt32.MaxValue / values.Length); + + for (var i = 0; i < values.Length; i++) + values[i] = Int32.MinValue + increment * i; + + // tell the actor in reverse order + foreach (var value in values.Reverse()) + { + actor.Tell(value); + actor.Tell(value); + actor.Tell(value); + } + + //resume mailbox, this prevents the mailbox from running to early + actor.Tell(new Resume(null)); + + // expect the messages in the correct order + foreach (var value in values) + { + ExpectMsg(value); + ExpectMsg(value); + ExpectMsg(value); + } + + ExpectNoMsg(TimeSpan.FromSeconds(0.3)); + } } } diff --git a/src/core/Akka.Tests/Dispatch/XUnitAsyncTestsSanityCheck.cs b/src/core/Akka.Tests/Dispatch/XUnitAsyncTestsSanityCheck.cs new file mode 100644 index 00000000000..976fdb0941a --- /dev/null +++ b/src/core/Akka.Tests/Dispatch/XUnitAsyncTestsSanityCheck.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.Actor.Internal; +using Akka.TestKit; +using Xunit; + +namespace Akka.Tests.Dispatch +{ + public class XUnitAsyncTestsSanityCheck : AkkaSpec + { + [Fact] + public async Task AsyncTestsShouldNotLoseAmbientContext() + { + var ambientContext = InternalCurrentActorCellKeeper.Current; + var backgroundOps = new List(); + for (var c = 0; c < 50; c++) + { + backgroundOps.Add(Task.Factory.StartNew(async () => + { + for (var t = 0; t < 1000; t++) + await Task.Delay(1); + })); + } + for (var t = 0; t < 1000; t++) + { + Assert.Equal(ambientContext, InternalCurrentActorCellKeeper.Current); + await Task.Delay(1); + } + await Task.WhenAll(backgroundOps); + } + } +} diff --git a/src/core/Akka.Tests/Routing/ConsistentHashingRouterSpec.cs b/src/core/Akka.Tests/Routing/ConsistentHashingRouterSpec.cs index e4efcf1e8d1..f85b117a23e 100644 --- a/src/core/Akka.Tests/Routing/ConsistentHashingRouterSpec.cs +++ b/src/core/Akka.Tests/Routing/ConsistentHashingRouterSpec.cs @@ -225,15 +225,13 @@ public void ConsistentHashingGroupRouterMustSelectDestinationWithDefinedHashMapp } [Fact] - public async Task ConsistentHashingRouterMustAdjustNodeRingWhenRouteeDies() + public void ConsistentHashingRouterMustAdjustNodeRingWhenRouteeDies() { //create pool router with two routees var router5 = Sys.ActorOf(Props.Create().WithRouter(new ConsistentHashingPool(2, null, null, null)), "router5"); - //verify that we have at least 2 routees - var currentRoutees = await router5.Ask(new GetRoutees(), GetTimeoutOrDefault(null)); - currentRoutees.Members.Count().ShouldBe(2); + ((RoutedActorRef)router5).Children.Count().ShouldBe(2); router5.Tell(new Msg("a", "A"), TestActor); var actorWhoDies = ExpectMsg(); @@ -249,8 +247,6 @@ public async Task ConsistentHashingRouterMustAdjustNodeRingWhenRouteeDies() var actorWhoDidntDie = ExpectMsg(TimeSpan.FromMilliseconds(50)); actorWhoDidntDie.ShouldNotBe(actorWhoDies); }, TimeSpan.FromSeconds(5)); - - } } } diff --git a/src/core/Akka.Tests/Routing/ResizerSpec.cs b/src/core/Akka.Tests/Routing/ResizerSpec.cs index 1fd62b32961..bec5495faec 100644 --- a/src/core/Akka.Tests/Routing/ResizerSpec.cs +++ b/src/core/Akka.Tests/Routing/ResizerSpec.cs @@ -159,34 +159,37 @@ public void DefaultResizer_must_grow_as_needed_under_pressure() (RouteeSize(router)).ShouldBe(resizer.LowerBound); - Action loop = (loops, span) => + Action loop = (loops, span, expectedBound) => { for (var i = 0; i < loops; i++) { router.Tell(span, TestActor); + if (expectedBound.HasValue && RouteeSize(router) >= expectedBound.Value) + { + return; + } + //sending too quickly will result in skipped resize due to many resizeInProgress conflicts Thread.Sleep(TimeSpan.FromMilliseconds(20)); } - Within( - TimeSpan.FromMilliseconds((span.TotalMilliseconds * loops) / resizer.LowerBound) + TimeSpan.FromSeconds(2), - () => - { - for (var i = 0; i < loops; i++) ExpectMsg("done"); - return true; - }); - }; - + var max = TimeSpan.FromMilliseconds((span.TotalMilliseconds*loops)/resizer.LowerBound) + + TimeSpan.FromSeconds(2); + + Within(max, () => + { + for (var i = 0; i < loops; i++) ExpectMsg("done"); + return true; + }); + }; // 2 more should go through without triggering more - loop(2, TimeSpan.FromMilliseconds(200)); + loop(2, TimeSpan.FromMilliseconds(200), null); RouteeSize(router).ShouldBe(resizer.LowerBound); - // a whole bunch should max it out - loop(50, TimeSpan.FromMilliseconds(500)); + loop(100, TimeSpan.FromMilliseconds(500), resizer.UpperBound); RouteeSize(router).ShouldBe(resizer.UpperBound); - } class BackoffActor : UntypedActor @@ -213,9 +216,12 @@ public void DefaultResizer_must_backoff() var router = Sys.ActorOf(Props.Create().WithRouter(new RoundRobinPool(0, resizer))); // put some pressure on the router - for (var i = 0; i < 25; i++) + for (var i = 0; i < 50; i++) { router.Tell(150); + if (RouteeSize(router) > 2) + break; + Thread.Sleep(20); } diff --git a/src/core/Akka.Tests/Routing/RoundRobinSpec.cs b/src/core/Akka.Tests/Routing/RoundRobinSpec.cs index 87297644028..4defe5babd3 100644 --- a/src/core/Akka.Tests/Routing/RoundRobinSpec.cs +++ b/src/core/Akka.Tests/Routing/RoundRobinSpec.cs @@ -62,6 +62,21 @@ public void RoundRobin_must_be_able_to_shut_down_its_instance() Sys.Stop(router); testLatch.Ready(TimeSpan.FromSeconds(5)); } + + [Fact] + public void RoundRobin_should_not_throw_IndexOutOfRangeException_when_counter_wraps_to_be_negative() + { + Assert.DoesNotThrow( + () => + { + var routees = new[] {Routee.NoRoutee, Routee.NoRoutee, Routee.NoRoutee}; + var routingLogic = new RoundRobinRoutingLogic(int.MaxValue - 5); + for (var i = 0; i < 10; i++) + { + routingLogic.Select(i, routees); + } + }); + } } } diff --git a/src/core/Akka.Tests/Routing/RoutingSpec.cs b/src/core/Akka.Tests/Routing/RoutingSpec.cs index fdba1c6af9a..91663f44f54 100644 --- a/src/core/Akka.Tests/Routing/RoutingSpec.cs +++ b/src/core/Akka.Tests/Routing/RoutingSpec.cs @@ -7,7 +7,6 @@ using System; using System.Linq; -using System.Threading.Tasks; using Akka.Actor; using Akka.Routing; using Akka.TestKit; @@ -65,24 +64,28 @@ protected override void OnReceive(object message) public void Router_in_general_must_evict_terminated_routees() { var router = Sys.ActorOf(new RoundRobinPool(2).Props(Props.Create()), "router"); - router.Tell("",TestActor); - router.Tell("",TestActor); + router.Tell("", TestActor); + router.Tell("", TestActor); + var c1 = ExpectMsg(); var c2 = ExpectMsg(); + Watch(router); Watch(c2); Sys.Stop(c2); + + AwaitCondition(() => ((RoutedActorRef) router).Children.Count() == 1); + ExpectTerminated(c2).ExistenceConfirmed.ShouldBe(true); - // it might take a while until the Router has actually processed the Terminated message - Task.Delay(100).Wait(); - AwaitCondition(() => - { - router.Tell("", TestActor); - router.Tell("", TestActor); - var res = ReceiveWhile(TimeSpan.FromMilliseconds(100), o => o is IActorRef ? (IActorRef) o : ActorRefs.NoSender, 2); - return res.SequenceEqual(new[] {c1, c1}); - }); - + + router.Tell("", TestActor); + var msg1 = ExpectMsg(); + msg1.ShouldBe(c1); + + router.Tell("", TestActor); + var msg2 = ExpectMsg(); + msg2.ShouldBe(c1); + Sys.Stop(c1); ExpectTerminated(router).ExistenceConfirmed.ShouldBe(true); } diff --git a/src/core/Akka.Tests/Routing/SmallestMailboxSpec.cs b/src/core/Akka.Tests/Routing/SmallestMailboxSpec.cs index a5ab8ba4a83..87ebb0563b7 100644 --- a/src/core/Akka.Tests/Routing/SmallestMailboxSpec.cs +++ b/src/core/Akka.Tests/Routing/SmallestMailboxSpec.cs @@ -83,6 +83,21 @@ public void Smallest_mailbox_router_must_deliver_messages_to_idle_actor() Assert.NotEqual(path2, busyPath); Assert.NotEqual(path3, busyPath); } + + [Fact] + public void SmallestMail_should_not_throw_IndexOutOfRangeException_when_counter_wraps_to_be_negative() + { + Assert.DoesNotThrow( + () => + { + var routees = new[] {Routee.NoRoutee, Routee.NoRoutee, Routee.NoRoutee}; + var routingLogic = new SmallestMailboxRoutingLogic(int.MaxValue - 5); + for (var i = 0; i < 10; i++) + { + routingLogic.Select(i, routees); + } + }); + } } } diff --git a/src/core/Akka.Tests/Routing/TailChoppingSpec.cs b/src/core/Akka.Tests/Routing/TailChoppingSpec.cs index aeb48634dad..c74d5213ad8 100644 --- a/src/core/Akka.Tests/Routing/TailChoppingSpec.cs +++ b/src/core/Akka.Tests/Routing/TailChoppingSpec.cs @@ -8,7 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; +using System.Threading.Tasks; using Akka.Actor; using Akka.Routing; using Akka.TestKit; @@ -19,60 +19,54 @@ namespace Akka.Tests.Routing { public class TailChoppingSpec : AkkaSpec { - private TestActor testActor; - - private ActorSystem actorSystem; - - class TailChopTestActor : UntypedActor + class TailChopTestActor : ReceiveActor { - private int timesResponded; - - private int sleepTime; + private int _timesResponded; public TailChopTestActor(int sleepTime) { - this.sleepTime = sleepTime; - } - - protected override void OnReceive(object message) - { - var command = message as string; - switch (command) + Receive(async command => { - case "stop": - Context.Stop(Self); - break; - case "times": - Sender.Tell(timesResponded); - break; - default: - Thread.Sleep(sleepTime); - Sender.Tell("ack"); - timesResponded++; - break; - } + switch (command) + { + case "stop": + Context.Stop(Self); + break; + case "times": + Sender.Tell(_timesResponded); + break; + default: + await Task.Delay(sleepTime); + Sender.Tell("ack"); + _timesResponded++; + break; + } + }); } } public class BroadcastTarget : UntypedActor { - private AtomicCounter _counter; - private TestLatch _latch; + private readonly AtomicCounter _counter; + private readonly TestLatch _latch; + public BroadcastTarget(TestLatch latch, AtomicCounter counter) { _latch = latch; _counter = counter; } + protected override void OnReceive(object message) { - if (message is string) + var messageString = message as string; + if (messageString != null) { - var s = (string)message; - if (s == "end") + if (messageString == "end") { _latch.CountDown(); } } + if (message is int) { var i = (int)message; @@ -93,8 +87,8 @@ public Func, bool> OneOfShouldEqual(int what, IEnumerable { - var results = actors.Select(x => func(x)); - return (results.Any(x => x == what)); + var results = actors.Select(func); + return results.Any(x => x == what); }; } @@ -102,8 +96,8 @@ public Func, bool> AllShouldEqual(int what, IEnumerable { - var results = actors.Select(x => func(x)); - return (results.All(x => x == what)); + var results = actors.Select(func); + return results.All(x => x == what); }; } @@ -118,7 +112,7 @@ public void Tail_chopping_router_must_deliver_a_broadcast_message_using_tell() var actor2 = Sys.ActorOf(Props.Create(() => new BroadcastTarget(doneLatch, counter2)), "Actor2"); var routedActor = Sys.ActorOf(Props.Create() - .WithRouter(new TailChoppingGroup(new string[] { actor1.Path.ToString(), actor2.Path.ToString() }, TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(100)) + .WithRouter(new TailChoppingGroup(new[] { actor1.Path.ToString(), actor2.Path.ToString() }, TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(100)) )); routedActor.Tell(new Broadcast(1)); @@ -138,7 +132,7 @@ public void Tail_chopping_router_must_return_response_from_second_actor_after_in var probe = CreateTestProbe(); var routedActor = Sys.ActorOf(Props.Create() - .WithRouter(new TailChoppingGroup(new string[] { actor1.Path.ToString(), actor2.Path.ToString() }, TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(50)) + .WithRouter(new TailChoppingGroup(new[] { actor1.Path.ToString(), actor2.Path.ToString() }, TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(50)) )); probe.Send(routedActor, ""); @@ -157,15 +151,23 @@ public void Tail_chopping_router_must_throw_exception_if_no_result_will_arrive_w var actor2 = Sys.ActorOf(Props.Create(() => new TailChopTestActor(500)), "Actor6"); var probe = CreateTestProbe(); + var routedActor = Sys.ActorOf(Props.Create() - .WithRouter(new TailChoppingGroup(new string[] { actor1.Path.ToString(), actor2.Path.ToString() }, TimeSpan.FromMilliseconds(300), TimeSpan.FromMilliseconds(50)) - )); + .WithRouter(new TailChoppingGroup( + new[] + { + actor1.Path.ToString(), + actor2.Path.ToString() + }, + TimeSpan.FromMilliseconds(300), + TimeSpan.FromMilliseconds(50)) + )); probe.Send(routedActor, ""); - probe.ExpectMsg(); + probe.ExpectMsg(TimeSpan.FromMilliseconds(700)); var actorList = new List { actor1, actor2 }; - Assert.True(AllShouldEqual(1, actorList)((x => (int)x.Ask("times").Result))); + Assert.True(AllShouldEqual(1, actorList)(x => (int) x.Ask("times").Result)); routedActor.Tell(new Broadcast("stop")); } diff --git a/src/core/Akka.Tests/Serialization/SerializationSpec.cs b/src/core/Akka.Tests/Serialization/SerializationSpec.cs index 51172235176..d6d4dc4e5a4 100644 --- a/src/core/Akka.Tests/Serialization/SerializationSpec.cs +++ b/src/core/Akka.Tests/Serialization/SerializationSpec.cs @@ -206,7 +206,7 @@ public void CanSerializeIntMessage() [Fact] public void CanSerializeLong() { - var message = 123l; + var message = 123L; AssertEqual(message); } @@ -510,7 +510,7 @@ private static string GetConfig() akka.actor { serializers { dummy = """ + typeof(DummySerializer).AssemblyQualifiedName + @""" - } + } serialization-bindings { ""System.String"" = dummy diff --git a/src/core/Akka/Actor/ActorBase.SupervisorStrategy.cs b/src/core/Akka/Actor/ActorBase.SupervisorStrategy.cs index be6e80d056c..eae787359c8 100644 --- a/src/core/Akka/Actor/ActorBase.SupervisorStrategy.cs +++ b/src/core/Akka/Actor/ActorBase.SupervisorStrategy.cs @@ -14,7 +14,7 @@ public abstract partial class ActorBase /// /// Gets or sets a . /// When getting, if a previously has been set it's returned; otherwise calls - /// SupervisorStratregy(), stores and returns it. + /// SupervisorStrategy(), stores and returns it. /// internal SupervisorStrategy SupervisorStrategyInternal { diff --git a/src/core/Akka/Actor/ActorCell.DeathWatch.cs b/src/core/Akka/Actor/ActorCell.DeathWatch.cs index 7fd63f4154c..ff6cbcfbe1d 100644 --- a/src/core/Akka/Actor/ActorCell.DeathWatch.cs +++ b/src/core/Akka/Actor/ActorCell.DeathWatch.cs @@ -6,7 +6,6 @@ //----------------------------------------------------------------------- using System; -using System.Collections.Generic; using System.Linq; using Akka.Dispatch.SysMsg; using Akka.Event; @@ -15,9 +14,7 @@ namespace Akka.Actor { partial class ActorCell { - HashSet _watching = new HashSet(); - readonly HashSet _watchedBy = new HashSet(); - HashSet _terminatedQueue = new HashSet();//terminatedqueue should never be used outside the message loop + private IActorState _state = new DefaultActorState(); public IActorRef Watch(IActorRef subject) { @@ -28,7 +25,7 @@ public IActorRef Watch(IActorRef subject) MaintainAddressTerminatedSubscription(() => { a.Tell(new Watch(a, Self)); // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS - _watching.Add(a); + _state = _state.AddWatching(a); }, a); } return a; @@ -37,25 +34,25 @@ public IActorRef Watch(IActorRef subject) public IActorRef Unwatch(IActorRef subject) { var a = (IInternalActorRef)subject; - if (! a.Equals(Self) && WatchingContains(a)) + if (!a.Equals(Self) && WatchingContains(a)) { a.Tell(new Unwatch(a, Self)); MaintainAddressTerminatedSubscription(() => { - _watching = RemoveFromSet(a, _watching); + _state = _state.RemoveWatching(a); }, a); } - _terminatedQueue = RemoveFromSet(a, _terminatedQueue); + _state = _state.RemoveTerminated(a); return a; } protected void ReceivedTerminated(Terminated t) { - if (_terminatedQueue.Contains(t.ActorRef)) - { - _terminatedQueue.Remove(t.ActorRef); // here we know that it is the SAME ref which was put in - ReceiveMessage(t); - } + if (!_state.ContainsTerminated(t.ActorRef)) + return; + + _state = _state.RemoveTerminated(t.ActorRef); // here we know that it is the SAME ref which was put in + ReceiveMessage(t); } /// @@ -68,7 +65,7 @@ protected void WatchedActorTerminated(IActorRef actor, bool existenceConfirmed, { MaintainAddressTerminatedSubscription(() => { - _watching = RemoveFromSet(actor, _watching); + _state = _state.RemoveWatching(actor); }, actor); if (!IsTerminating) { @@ -84,30 +81,22 @@ protected void WatchedActorTerminated(IActorRef actor, bool existenceConfirmed, public void TerminatedQueuedFor(IActorRef subject) { - _terminatedQueue.Add(subject); + _state = _state.AddTerminated(subject); } private bool WatchingContains(IActorRef subject) { - return _watching.Contains(subject) || - (subject.Path.Uid != ActorCell.UndefinedUid && _watching.Contains(new UndefinedUidActorRef(subject))); - } - - private HashSet RemoveFromSet(IActorRef subject, HashSet set) - { - if (subject.Path.Uid != ActorCell.UndefinedUid) - { - set.Remove(subject); - set.Remove(new UndefinedUidActorRef(subject)); - return set; - } - - return new HashSet(set.Where(a => !a.Path.Equals(subject.Path))); + return _state.ContainsWatching(subject) || + (subject.Path.Uid != UndefinedUid && _state.ContainsWatching(new UndefinedUidActorRef(subject))); } protected void TellWatchersWeDied() { - if (_watchedBy.Count==0) return; + var watchedBy = _state + .GetWatchedBy() + .ToList(); + + if (!watchedBy.Any()) return; try { // Don't need to send to parent parent since it receives a DWN by default @@ -125,12 +114,12 @@ protected void TellWatchersWeDied() * * If the remote watchers are notified first, then the mailbox of the Remoting will guarantee the correct order. */ - foreach (var w in _watchedBy) SendTerminated(false, w); - foreach (var w in _watchedBy) SendTerminated(true, w); + foreach (var w in watchedBy) SendTerminated(false, w); + foreach (var w in watchedBy) SendTerminated(true, w); } finally { - _watching = new HashSet(); + _state = _state.ClearWatching(); } } @@ -138,25 +127,30 @@ private void SendTerminated(bool ifLocal, IActorRef watcher) { if (((IActorRefScope)watcher).IsLocal == ifLocal && !watcher.Equals(Parent)) { - ((IInternalActorRef)watcher).Tell(new DeathWatchNotification(Self, true, false)); + watcher.Tell(new DeathWatchNotification(Self, true, false)); } } protected void UnwatchWatchedActors(ActorBase actor) { - if(_watching.Count==0) return; + var watching = _state + .GetWatching() + .ToList(); + + if (!watching.Any()) return; + MaintainAddressTerminatedSubscription(() => { try { - foreach ( // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS - var watchee in _watching.OfType()) + // ➡➡➡ NEVER SEND THE SAME SYSTEM MESSAGE OBJECT TO TWO ACTORS + foreach (var watchee in watching.OfType()) watchee.Tell(new Unwatch(watchee, Self)); } finally { - _watching = new HashSet(); - _terminatedQueue = new HashSet(); + _state = _state.ClearWatching(); + _state = _state.ClearTerminated(); } }); } @@ -168,11 +162,13 @@ protected void AddWatcher(IActorRef watchee, IActorRef watcher) if (watcheeSelf && !watcherSelf) { - if(!_watchedBy.Contains(watcher)) MaintainAddressTerminatedSubscription(() => - { - _watchedBy.Add(watcher); - if(System.Settings.DebugLifecycle) Publish(new Debug(Self.Path.ToString(), Actor.GetType(), string.Format("now watched by {0}", watcher))); - }, watcher); + if (!_state.ContainsWatchedBy(watcher)) MaintainAddressTerminatedSubscription(() => + { + //_watchedBy.Add(watcher); + _state = _state.AddWatchedBy(watcher); + + if (System.Settings.DebugLifecycle) Publish(new Debug(Self.Path.ToString(), Actor.GetType(), string.Format("now watched by {0}", watcher))); + }, watcher); } else if (!watcheeSelf && watcherSelf) { @@ -191,11 +187,13 @@ protected void RemWatcher(IActorRef watchee, IActorRef watcher) if (watcheeSelf && !watcherSelf) { - if( _watchedBy.Contains(watcher)) MaintainAddressTerminatedSubscription(() => + if (_state.ContainsWatchedBy(watcher)) MaintainAddressTerminatedSubscription(() => { - _watchedBy.Remove(watcher); + //_watchedBy.Remove(watcher); + _state = _state.RemoveWatchedBy(watcher); + if (System.Settings.DebugLifecycle) Publish(new Debug(Self.Path.ToString(), Actor.GetType(), string.Format("no longer watched by {0}", watcher))); - } , watcher); + }, watcher); } else if (!watcheeSelf && watcherSelf) { @@ -212,7 +210,11 @@ protected void AddressTerminated(Address address) // cleanup watchedBy since we know they are dead MaintainAddressTerminatedSubscription(() => { - foreach (var a in _watchedBy.Where(a => a.Path.Address == address)) _watchedBy.Remove(a); + foreach (var a in _state.GetWatchedBy().Where(a => a.Path.Address == address)) + { + //_watchedBy.Remove(a); + _state = _state.RemoveWatchedBy(a); + } }); // send DeathWatchNotification to self for all matching subjects @@ -221,7 +223,7 @@ protected void AddressTerminated(Address address) // When a parent is watching a child and it terminates due to AddressTerminated // it is removed by sending DeathWatchNotification with existenceConfirmed = true to support // immediate creation of child with same name. - foreach(var a in _watching.Where(a => a.Path.Address == address)) + foreach (var a in _state.GetWatching().Where(a => a.Path.Address == address)) { Self.Tell(new DeathWatchNotification(a, true /*TODO: childrenRefs.getByRef(a).isDefined*/, true)); } @@ -233,15 +235,18 @@ protected void AddressTerminated(Address address) /// Ends subscription to AddressTerminated if subscribing and the /// block removes the last non-local ref from watching and watchedBy. /// - private void MaintainAddressTerminatedSubscription(Action block, IActorRef change= null) + private void MaintainAddressTerminatedSubscription(Action block, IActorRef change = null) { if (IsNonLocal(change)) { var had = HasNonLocalAddress(); block(); var has = HasNonLocalAddress(); - if (had && !has) UnsubscribeAddressTerminated(); - else if (!had && has) SubscribeAddressTerminated(); + + if (had && !has) + UnsubscribeAddressTerminated(); + else if (!had && has) + SubscribeAddressTerminated(); } else { @@ -251,15 +256,18 @@ private void MaintainAddressTerminatedSubscription(Action block, IActorRef chang private static bool IsNonLocal(IActorRef @ref) { - if (@ref == null) return true; + if (@ref == null) + return true; + var a = @ref as IInternalActorRef; - if (a != null && !a.IsLocal) return true; - return false; + return a != null && !a.IsLocal; } private bool HasNonLocalAddress() { - return _watching.Any(IsNonLocal) || _watchedBy.Any(IsNonLocal); + var watching = _state.GetWatching(); + var watchedBy = _state.GetWatchedBy(); + return watching.Any(IsNonLocal) || watchedBy.Any(IsNonLocal); } private void UnsubscribeAddressTerminated() @@ -295,7 +303,4 @@ public override IActorRefProvider Provider } } } - - -} - +} \ No newline at end of file diff --git a/src/core/Akka/Actor/ActorCell.DefaultMessages.cs b/src/core/Akka/Actor/ActorCell.DefaultMessages.cs index 01972b7d7e8..9b2e36d4279 100644 --- a/src/core/Akka/Actor/ActorCell.DefaultMessages.cs +++ b/src/core/Akka/Actor/ActorCell.DefaultMessages.cs @@ -122,7 +122,7 @@ public void ReceiveMessageForTest(Envelope envelope) internal void ReceiveMessage(object message) { - var wasHandled = _actor.AroundReceive(_behaviorStack.Peek(), message); + var wasHandled = _actor.AroundReceive(_state.GetCurrentBehavior(), message); if (System.Settings.AddLoggingReceive && _actor is ILogReceive) { diff --git a/src/core/Akka/Actor/ActorCell.FaultHandling.cs b/src/core/Akka/Actor/ActorCell.FaultHandling.cs index 07f1f746100..1d429d67e55 100644 --- a/src/core/Akka/Actor/ActorCell.FaultHandling.cs +++ b/src/core/Akka/Actor/ActorCell.FaultHandling.cs @@ -229,7 +229,7 @@ private void HandleInvokeFailure(Exception cause, IEnumerable childre if (CurrentMessage is Failed) { var failedChild = Sender; - childrenNotToSuspend = childrenNotToSuspend.Concat(failedChild); //Function handles childrenNotToSuspend beeing null + childrenNotToSuspend = childrenNotToSuspend.Concat(failedChild); //Function handles childrenNotToSuspend being null SetFailed(failedChild); } else @@ -305,6 +305,7 @@ private void FinishTerminate() SwapMailbox(deadLetterMailbox); mailbox.BecomeClosed(); mailbox.CleanUp(); + Dispatcher.Detach(this); } } finally diff --git a/src/core/Akka/Actor/ActorCell.cs b/src/core/Akka/Actor/ActorCell.cs index 8d9b9cf9e9f..21008ef9be1 100644 --- a/src/core/Akka/Actor/ActorCell.cs +++ b/src/core/Akka/Actor/ActorCell.cs @@ -24,7 +24,7 @@ public partial class ActorCell : IUntypedActorContext, ICell private Props _props; private static readonly Props terminatedProps=new TerminatedProps(); - private Stack _behaviorStack = new Stack(1); + private long _uid; private ActorBase _actor; private bool _actorHasBeenCleared; @@ -69,6 +69,7 @@ internal static ActorCell Current public void Init(bool sendSupervise, Func createMailbox /*, MailboxType mailboxType*/) //TODO: switch from Func createMailbox to MailboxType mailboxType { var mailbox = createMailbox(); //Akka: dispatcher.createMailbox(this, mailboxType) + Dispatcher.Attach(this); mailbox.Setup(Dispatcher); mailbox.SetActor(this); _mailbox = mailbox; @@ -145,14 +146,12 @@ public IEnumerable GetChildren() public void Become(Receive receive) { - if(_behaviorStack.Count > 1) //We should never pop off the initial receiver - _behaviorStack.Pop(); - _behaviorStack.Push(receive); + _state = _state.Become(receive); } public void BecomeStacked(Receive receive) { - _behaviorStack.Push(receive); + _state = _state.BecomeStacked(receive); } @@ -173,8 +172,7 @@ void IActorContext.Unbecome() public void UnbecomeStacked() { - if (_behaviorStack.Count > 1) //We should never pop off the initial receiver - _behaviorStack.Pop(); + _state = _state.UnbecomeStacked(); } void IUntypedActorContext.Become(UntypedReceive receive) @@ -209,7 +207,7 @@ private ActorBase NewActor() //set the thread static context or things will break UseThreadContext(() => { - _behaviorStack = new Stack(1); + _state = _state.ClearBehaviorStack(); instance = CreateNewActorInstance(); instance.SupervisorStrategyInternal = _props.SupervisorStrategy; //defaults to null - won't affect lazy instantiation unless explicitly set in props @@ -307,12 +305,14 @@ protected void ClearActor(ActorBase actor) } _actorHasBeenCleared = true; CurrentMessage = null; - _behaviorStack = null; + + //TODO: semantics here? should all "_state" be cleared? or just behavior? + _state = _state.ClearBehaviorStack(); } protected void PrepareForNewActor() { - _behaviorStack = new Stack(1); + _state = _state.ClearBehaviorStack(); _actorHasBeenCleared = false; } protected void SetActorFields(ActorBase actor) diff --git a/src/core/Akka/Actor/ActorRef.cs b/src/core/Akka/Actor/ActorRef.cs index 464dcec6d78..e3e96650643 100644 --- a/src/core/Akka/Actor/ActorRef.cs +++ b/src/core/Akka/Actor/ActorRef.cs @@ -142,7 +142,8 @@ public static void Tell(this IActorRef receiver, object message) /// /// Forwards the message using the current Sender /// - /// + /// The actor that receives the forward + /// The message to forward public static void Forward(this IActorRef receiver, object message) { var sender = ActorCell.GetCurrentSenderOrNoSender(); diff --git a/src/core/Akka/Actor/ActorRefProvider.cs b/src/core/Akka/Actor/ActorRefProvider.cs index 1df6a6f1f49..351acd54b89 100644 --- a/src/core/Akka/Actor/ActorRefProvider.cs +++ b/src/core/Akka/Actor/ActorRefProvider.cs @@ -429,7 +429,7 @@ public IInternalActorRef ActorOf(ActorSystemImpl system, Props props, IInternalA routedActorRef.Initialize(async); return routedActorRef; } - catch (Exception ex) + catch (Exception) { throw new ConfigurationException(string.Format("Configuration problem while creating [{0}] with router dispatcher [{1}] and mailbox {2}" + " and routee dispatcher [{3}] and mailbox [{4}].", path, routerProps.Dispatcher, routerProps.Mailbox, diff --git a/src/core/Akka/Actor/ActorSelection.cs b/src/core/Akka/Actor/ActorSelection.cs index 39975219551..d918d1b8243 100644 --- a/src/core/Akka/Actor/ActorSelection.cs +++ b/src/core/Akka/Actor/ActorSelection.cs @@ -211,6 +211,7 @@ public class ActorSelectionMessage : IAutoReceivedMessage, IPossiblyHarmful ///
/// The message. /// The elements. + /// public ActorSelectionMessage(object message, SelectionPathElement[] elements, bool wildCardFanOut = false) { Message = message; diff --git a/src/core/Akka/Actor/Address.cs b/src/core/Akka/Actor/Address.cs index 1ce3bf08534..41c34dc684e 100644 --- a/src/core/Akka/Actor/Address.cs +++ b/src/core/Akka/Actor/Address.cs @@ -220,9 +220,9 @@ public static IEnumerable Unapply(string addr) var finalAddr = addr; // need to add a special case for URI fragments containing #, since those don't get counted // as relative URIs by C# - if(Uri.IsWellFormedUriString(addr, UriKind.Absolute) || (!Uri.IsWellFormedUriString(addr, UriKind.Relative) + if(Uri.IsWellFormedUriString(addr, UriKind.Absolute) || (!Uri.IsWellFormedUriString(addr, UriKind.Relative) && !addr.Contains("#"))) return null; - if(!addr.StartsWith("/")) + if(!addr.StartsWith("/")) { //hack to cause the URI not to explode when we're only given an actor name finalAddr = "/" + addr; @@ -230,7 +230,7 @@ public static IEnumerable Unapply(string addr) return finalAddr.Split('/').SkipWhile(string.IsNullOrEmpty); } - catch (UriFormatException ex) + catch (UriFormatException) { return null; } diff --git a/src/core/Akka/Actor/Cancellation/ICancellable.cs b/src/core/Akka/Actor/Cancellation/ICancellable.cs index a3570371a2b..de45a4d57d0 100644 --- a/src/core/Akka/Actor/Cancellation/ICancellable.cs +++ b/src/core/Akka/Actor/Cancellation/ICancellable.cs @@ -11,7 +11,7 @@ namespace Akka.Actor { /// - /// Siginifies something that can be canceled + /// Signifies something that can be canceled /// public interface ICancelable { diff --git a/src/core/Akka/Actor/Futures.cs b/src/core/Akka/Actor/Futures.cs index 6124571ee95..18d4b46edeb 100644 --- a/src/core/Akka/Actor/Futures.cs +++ b/src/core/Akka/Actor/Futures.cs @@ -29,15 +29,14 @@ public static Task Ask(this ICanTell self, object message, TimeSpan? tim return self.Ask(message, timeout); } - public static async Task Ask(this ICanTell self, object message, TimeSpan? timeout = null) + public static Task Ask(this ICanTell self, object message, TimeSpan? timeout = null) { IActorRefProvider provider = ResolveProvider(self); if (provider == null) throw new NotSupportedException("Unable to resolve the target Provider"); ResolveReplyTo(); - var result = (T)await Ask(self, message, provider, timeout); - return result; + return Ask(self, message, provider, timeout).CastTask(); } internal static IActorRef ResolveReplyTo() diff --git a/src/core/Akka/Actor/GracefulStopSupport.cs b/src/core/Akka/Actor/GracefulStopSupport.cs index 53fb340314a..938072f3881 100644 --- a/src/core/Akka/Actor/GracefulStopSupport.cs +++ b/src/core/Akka/Actor/GracefulStopSupport.cs @@ -23,7 +23,7 @@ namespace Akka.Actor /// IMPORTANT: the actor being terminated and its supervisor being informed of the availability of the deceased actor's name /// are two distinct operations, which do not obey any reliable ordering. /// - /// If the target actor isn't terminated within the timeout the is complted with failure. + /// If the target actor isn't terminated within the timeout the is completed with failure. /// /// If you want to invoke specialized stopping logic on your target actor instead of , you can pass your stop command as a parameter: /// diff --git a/src/core/Akka/Actor/Inbox.cs b/src/core/Akka/Actor/Inbox.cs index 3546337223b..cd6b87c7d47 100644 --- a/src/core/Akka/Actor/Inbox.cs +++ b/src/core/Akka/Actor/Inbox.cs @@ -349,13 +349,18 @@ private object AwaitResult(Task task, TimeSpan timeout) { if (task.Wait(timeout)) { + var received = task.Result as Status.Failure; + if (received != null && received.Cause is TimeoutException) + { + var reason = string.Format("Inbox {0} received a status failure response message: {1}", Receiver.Path, received.Cause.Message); + throw new TimeoutException(reason, received.Cause); + } + return task.Result; } - else - { - var fmt = string.Format("Inbox {0} didn't received a response message in specified timeout {1}", Receiver.Path, timeout); - throw new TimeoutException(fmt); - } + + var fmt = string.Format("Inbox {0} didn't received a response message in specified timeout {1}", Receiver.Path, timeout); + throw new TimeoutException(fmt); } } } diff --git a/src/core/Akka/Actor/Props.cs b/src/core/Akka/Actor/Props.cs index a106f5d3cf7..c4fffea1c4f 100644 --- a/src/core/Akka/Actor/Props.cs +++ b/src/core/Akka/Actor/Props.cs @@ -19,7 +19,7 @@ namespace Akka.Actor { /// - /// Props is a configuration object using in creating an [[Actor]]; it is + /// Props is a configuration object used in creating an [[Actor]]; it is /// immutable, so it is thread-safe and fully shareable. /// Examples on C# API: /// @@ -91,7 +91,7 @@ private bool CompareArguments(Props other) return false; //TODO: since arguments can be serialized, we can not compare by ref - //arguments may also not impement equality opertators, so we can not structurally compare either + //arguments may also not implement equality operators, so we can not structurally compare either //we can not just call a serializer and compare outputs either, since different args may require diff serializer mechanics return true; @@ -333,7 +333,7 @@ public static Props Empty /// /// Creates the specified factory. /// - /// The type of the t actor. + /// The type of the actor. /// The factory. /// Optional: Supervisor strategy /// Props. @@ -355,7 +355,7 @@ public static Props Create(Expression> factory, SupervisorS /// /// Creates this instance. /// - /// The type of the t actor. + /// The type of the actor. /// Props. public static Props Create(params object[] args) where TActor : ActorBase { @@ -377,7 +377,7 @@ public static Props CreateBy(params object[] args) where TProducer : /// /// Creates this instance. /// - /// The type of the t actor. + /// The type of the actor. /// Props. public static Props Create(SupervisorStrategy supervisorStrategy) where TActor : ActorBase, new() { @@ -390,6 +390,7 @@ public static Props CreateBy(params object[] args) where TProducer : /// Creates the specified type. /// /// The type. + /// /// Props. public static Props Create(Type type, params object[] args) { @@ -635,7 +636,7 @@ public override ActorBase NewActor() /// rather than a traditional Activator. /// Intended to be used in conjunction with Dependency Injection. /// - /// The type of the t actor. + /// The type of the actor. internal class DynamicProps : Props where TActor : ActorBase { /// diff --git a/src/core/Akka/Actor/RepointableActorRef.cs b/src/core/Akka/Actor/RepointableActorRef.cs index b5fadd7a60d..c28651f33c2 100644 --- a/src/core/Akka/Actor/RepointableActorRef.cs +++ b/src/core/Akka/Actor/RepointableActorRef.cs @@ -51,18 +51,18 @@ public override bool IsTerminated public void SwapUnderlying(ICell cell) { - #pragma warning disable 0420 +#pragma warning disable 0420 //Ok to ignore CS0420 "a reference to a volatile field will not be treated as volatile" for interlocked calls http://msdn.microsoft.com/en-us/library/4bw5ewxy(VS.80).aspx Interlocked.Exchange(ref _underlying_DoNotCallMeDirectly, cell); - #pragma warning restore 0420 +#pragma warning restore 0420 } private void SwapLookup(ICell cell) { - #pragma warning disable 0420 +#pragma warning disable 0420 //Ok to ignore CS0420 "a reference to a volatile field will not be treated as volatile" for interlocked calls http://msdn.microsoft.com/en-us/library/4bw5ewxy(VS.80).aspx Interlocked.Exchange(ref _lookup_DoNotCallMeDirectly, cell); - #pragma warning restore 0420 +#pragma warning restore 0420 } /// @@ -75,13 +75,13 @@ private void SwapLookup(ICell cell) public RepointableActorRef Initialize(bool async) { var underlying = Underlying; - if(underlying == null) + if (underlying == null) { var newCell = new UnstartedCell(_system, this, _props, _supervisor); SwapUnderlying(newCell); SwapLookup(newCell); _supervisor.Tell(new Supervise(this, async)); - if(!async) + if (!async) Point(); return this; @@ -101,11 +101,11 @@ public RepointableActorRef Initialize(bool async) public void Point() { var underlying = Underlying; - if(underlying == null) + if (underlying == null) throw new IllegalStateException("Underlying cell is null"); var unstartedCell = underlying as UnstartedCell; - if(unstartedCell != null) + if (unstartedCell != null) { // The problem here was that if the real actor (which will start running // at cell.start()) creates children in its constructor, then this may @@ -167,7 +167,7 @@ public bool IsStarted { get { - if(Underlying == null) + if (Underlying == null) throw new IllegalStateException("IsStarted called before initialized"); return !(Underlying is UnstartedCell); } @@ -181,32 +181,33 @@ protected override void TellInternal(object message, IActorRef sender) public override IActorRef GetChild(IEnumerable name) { var current = (IActorRef)this; - var index = 0; - foreach(var element in name) + if (!name.Any()) return current; + + var next = name.FirstOrDefault() ?? ""; + + switch (next) { - switch (element) - { - case "..": - return Parent.GetChild(name.Skip(index)); - case "": - break; - default: - var nameAndUid = ActorCell.SplitNameAndUid(element); - IChildStats stats; - if (Lookup.TryGetChildStatsByName(nameAndUid.Name, out stats)) + case "..": + return Parent.GetChild(name.Skip(1)); + case "": + return ActorRefs.Nobody; + default: + var nameAndUid = ActorCell.SplitNameAndUid(next); + IChildStats stats; + if (Lookup.TryGetChildStatsByName(nameAndUid.Name, out stats)) + { + var crs = stats as ChildRestartStats; + var uid = nameAndUid.Uid; + if (crs != null && (uid == ActorCell.UndefinedUid || uid == crs.Uid)) { - var crs = stats as ChildRestartStats; - var uid = nameAndUid.Uid; - if (crs != null && (uid == ActorCell.UndefinedUid || uid == crs.Uid)) - { - crs.Child.GetChild(name.Skip(index)); - } + if (name.Skip(1).Any()) + return crs.Child.GetChild(name.Skip(1)); + else + return crs.Child; } - return ActorRefs.Nobody; - } - index++; + } + return ActorRefs.Nobody; } - return current; } public override IInternalActorRef GetSingleChild(string name) @@ -242,11 +243,11 @@ public UnstartedCell(ActorSystemImpl system, RepointableActorRef self, Props pro public void ReplaceWith(ICell cell) { - lock(_lock) + lock (_lock) { try { - foreach(var envelope in _messageQueue) + foreach (var envelope in _messageQueue) { cell.Post(envelope.Sender, envelope.Message); } @@ -311,7 +312,7 @@ public bool TryGetChildStatsByName(string name, out IChildStats child) public void Post(IActorRef sender, object message) { - if(message is ISystemMessage) + if (message is ISystemMessage) SendSystemMessage(message, sender); else SendMessage(message, sender); @@ -319,12 +320,12 @@ public void Post(IActorRef sender, object message) private void SendMessage(object message, IActorRef sender) { - if(Monitor.TryEnter(_lock, _timeout)) + if (Monitor.TryEnter(_lock, _timeout)) { try { var cell = _self.Underlying; - if(CellIsReady(cell)) + if (CellIsReady(cell)) { cell.Post(sender, message); } @@ -348,10 +349,10 @@ private void SendMessage(object message, IActorRef sender) private void SendSystemMessage(object message, IActorRef sender) { - lock(_lock) + lock (_lock) { var cell = _self.Underlying; - if(CellIsReady(cell)) + if (CellIsReady(cell)) { cell.Post(sender, message); } @@ -361,14 +362,14 @@ private void SendSystemMessage(object message, IActorRef sender) try { // systemMessages that are sent during replace need to jump to just after the last system message in the queue, so it's processed before other messages - if(!ReferenceEquals(_self.Lookup, this) && ReferenceEquals(_self.Underlying, this) && + if (!ReferenceEquals(_self.Lookup, this) && ReferenceEquals(_self.Underlying, this) && _messageQueue.Count != 0) TryEnqueue(envelope); else _messageQueue.Add(envelope); Mailbox.DebugPrint("{0} temp queueing system msg {1} from {2}", Self, message, sender); } - catch(Exception e) + catch (Exception e) { _system.EventStream.Publish(new Warning(_self.Path.ToString(), GetType(), "Dropping message of type" + message.GetType() + " due to enqueue failure: " + e.ToString())); @@ -382,17 +383,17 @@ private void TryEnqueue(Envelope envelope) { var queueIndex = 0; var insertIntoIndex = -1; - while(true) + while (true) { var hasMoreMessagesInTheQueue = queueIndex < _messageQueue.Count; - if(hasMoreMessagesInTheQueue) + if (hasMoreMessagesInTheQueue) { var queuedMessage = _messageQueue[queueIndex]; queueIndex++; - if(queuedMessage.Message is ISystemMessage) + if (queuedMessage.Message is ISystemMessage) insertIntoIndex = queueIndex; } - else if(insertIntoIndex == -1) + else if (insertIntoIndex == -1) { _messageQueue.Add(envelope); return; @@ -425,7 +426,7 @@ public bool HasMessages { get { - lock(_lock) + lock (_lock) { var cell = _self.Underlying; return CellIsReady(cell) @@ -439,7 +440,7 @@ public int NumberOfMessages { get { - lock(_lock) + lock (_lock) { var cell = _self.Underlying; return CellIsReady(cell) diff --git a/src/core/Akka/Actor/Scheduler/DateTimeNowTimeProvider.cs b/src/core/Akka/Actor/Scheduler/DateTimeNowTimeProvider.cs index 0a29b271885..a77113a8add 100644 --- a/src/core/Akka/Actor/Scheduler/DateTimeNowTimeProvider.cs +++ b/src/core/Akka/Actor/Scheduler/DateTimeNowTimeProvider.cs @@ -15,6 +15,10 @@ public class DateTimeOffsetNowTimeProvider : ITimeProvider, IDateTimeOffsetNowTi private DateTimeOffsetNowTimeProvider() { } public DateTimeOffset Now { get { return DateTimeOffset.UtcNow; } } + public TimeSpan MonotonicClock {get { return Util.MonotonicClock.Elapsed; }} + + public TimeSpan HighResMonotonicClock{get { return Util.MonotonicClock.ElapsedHighRes; }} + public static DateTimeOffsetNowTimeProvider Instance { get { return _instance; } } } } diff --git a/src/core/Akka/Actor/Scheduler/ITimeProvider.cs b/src/core/Akka/Actor/Scheduler/ITimeProvider.cs index 6e5264a4c49..fb0ea5c42cf 100644 --- a/src/core/Akka/Actor/Scheduler/ITimeProvider.cs +++ b/src/core/Akka/Actor/Scheduler/ITimeProvider.cs @@ -15,6 +15,8 @@ public interface ITimeProvider /// Gets the scheduler's notion of current time. /// DateTimeOffset Now { get; } + TimeSpan MonotonicClock { get; } + TimeSpan HighResMonotonicClock { get; } } } diff --git a/src/core/Akka/Actor/Scheduler/SchedulerBase.cs b/src/core/Akka/Actor/Scheduler/SchedulerBase.cs index 1aa01cc15a1..f511e5e97fa 100644 --- a/src/core/Akka/Actor/Scheduler/SchedulerBase.cs +++ b/src/core/Akka/Actor/Scheduler/SchedulerBase.cs @@ -69,7 +69,10 @@ void IActionScheduler.ScheduleRepeatedly(TimeSpan initialDelay, TimeSpan interva DateTimeOffset ITimeProvider.Now { get { return TimeNow; } } + protected abstract DateTimeOffset TimeNow { get; } + public abstract TimeSpan MonotonicClock { get; } + public abstract TimeSpan HighResMonotonicClock { get; } protected abstract void InternalScheduleTellOnce(TimeSpan delay, ICanTell receiver, object message, IActorRef sender, ICancelable cancelable); diff --git a/src/core/Akka/Actor/Scheduler/SchedulerExtensions.cs b/src/core/Akka/Actor/Scheduler/SchedulerExtensions.cs index be19bdad368..88fe92228f3 100644 --- a/src/core/Akka/Actor/Scheduler/SchedulerExtensions.cs +++ b/src/core/Akka/Actor/Scheduler/SchedulerExtensions.cs @@ -17,7 +17,7 @@ public static class SchedulerExtensions /// The receiver. /// The message. /// The sender. - /// OPTIONAL. An that can be used to cancel sending of the message. Notye that once the message has been sent, it cannot be canceled. + /// OPTIONAL. An that can be used to cancel sending of the message. Note that once the message has been sent, it cannot be canceled. public static void ScheduleTellOnce(this ITellScheduler scheduler, int millisecondsDelay, ICanTell receiver, object message, IActorRef sender, ICancelable cancelable = null) { scheduler.ScheduleTellOnce(TimeSpan.FromMilliseconds(millisecondsDelay), receiver, message, sender, cancelable); @@ -31,7 +31,7 @@ public static void ScheduleTellOnce(this ITellScheduler scheduler, int milliseco /// The receiver. /// The message. /// The sender. - /// OPTIONAL. An that can be used to cancel sending of the message. Notye that once the message has been sent, it cannot be canceled. + /// OPTIONAL. An that can be used to cancel sending of the message. Note that once the message has been sent, it cannot be canceled. public static void ScheduleTellRepeatedly(this ITellScheduler scheduler, int initialMillisecondsDelay, int millisecondsInterval, ICanTell receiver, object message, IActorRef sender, ICancelable cancelable = null) { scheduler.ScheduleTellRepeatedly(TimeSpan.FromMilliseconds(initialMillisecondsDelay), TimeSpan.FromMilliseconds(millisecondsInterval), receiver, message, sender, cancelable); diff --git a/src/core/Akka/Actor/Scheduler/TaskBasedScheduler.cs b/src/core/Akka/Actor/Scheduler/TaskBasedScheduler.cs index 056a6bed774..ce9276af0c4 100644 --- a/src/core/Akka/Actor/Scheduler/TaskBasedScheduler.cs +++ b/src/core/Akka/Actor/Scheduler/TaskBasedScheduler.cs @@ -18,6 +18,8 @@ public class TaskBasedScheduler : SchedulerBase, IDateTimeOffsetNowTimeProvider { protected override DateTimeOffset TimeNow { get { return DateTimeOffset.Now; } } + public override TimeSpan MonotonicClock { get { return Util.MonotonicClock.Elapsed; } } + public override TimeSpan HighResMonotonicClock { get { return Util.MonotonicClock.ElapsedHighRes; } } protected override void InternalScheduleTellOnce(TimeSpan delay, ICanTell receiver, object message, IActorRef sender, ICancelable cancelable) { @@ -55,7 +57,7 @@ private void InternalScheduleOnce(TimeSpan initialDelay, Action action, Cancella { action(); } - catch(OperationCanceledException e) { } + catch(OperationCanceledException) { } //TODO: Should we log other exceptions? /@hcanber }, token, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current); diff --git a/src/core/Akka/Actor/SupervisorStrategy.cs b/src/core/Akka/Actor/SupervisorStrategy.cs index 67e10a859f2..fbe356dd36e 100644 --- a/src/core/Akka/Actor/SupervisorStrategy.cs +++ b/src/core/Akka/Actor/SupervisorStrategy.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using Akka.Actor.Internal; diff --git a/src/core/Akka/ActorState.cs b/src/core/Akka/ActorState.cs new file mode 100644 index 00000000000..4611f5bddf5 --- /dev/null +++ b/src/core/Akka/ActorState.cs @@ -0,0 +1,389 @@ +using System.Collections.Generic; + +namespace Akka.Actor +{ + /// + /// This interface represents the parts of the internal actor state; the behavior stack, watched by, watching and termination queue + /// + internal interface IActorState + { + /// + /// Removes the provided from the `Watching` set + /// + /// The to be removed + /// + IActorState RemoveWatching(IActorRef actor); + /// + /// Removes the provided from the `WatchedBy` set + /// + /// The to be removed + /// + IActorState RemoveWatchedBy(IActorRef actor); + /// + /// Removes the provided from the `Termination queue` set + /// + /// The to be removed + /// + IActorState RemoveTerminated(IActorRef actor); + /// + /// Adds the provided to the `Watching` set + /// + /// The to be added + /// + IActorState AddWatching(IActorRef actor); + /// + /// Adds the provided to the `WatchedBy` set + /// + /// The to be added + /// + IActorState AddWatchedBy(IActorRef actor); + /// + /// Adds the provided to the `Termination queue` set + /// + /// The to be added + /// + IActorState AddTerminated(IActorRef actor); + /// + /// Clears the `Watching` set + /// + /// + IActorState ClearWatching(); + /// + /// Clears the `Termination queue` set + /// + /// + IActorState ClearTerminated(); + /// + /// Clears the `Behavior` stack + /// + /// + IActorState ClearBehaviorStack(); + /// + /// Replaces the current receive behavior with a new behavior + /// + /// The new behavior + /// + IActorState Become(Receive receive); + /// + /// Pushes a new receive behavior onto the `Behavior` stack + /// + /// The new top level behavior + /// + IActorState BecomeStacked(Receive receive); + /// + /// Removes the top level receive behavior from the `Behavior` stack + /// + /// + IActorState UnbecomeStacked(); + /// + /// Determines whether the provided is present in the `Watching` set + /// + /// The to locate in the `Watching` set + /// + bool ContainsWatching(IActorRef actor); + /// + /// Determines whether the provided is present in the `WatchedBy` set + /// + /// The to locate in the `WatchedBy` set + /// + bool ContainsWatchedBy(IActorRef actor); + /// + /// Determines whether the provided is present in the `Termination queue` set + /// + /// The to locate in the `Termination queue` set + /// + bool ContainsTerminated(IActorRef actor); + /// + /// Returns an over the `Watching` set + /// + /// + IEnumerable GetWatching(); + /// + /// Returns an over the `WatchedBy` set + /// + /// + IEnumerable GetWatchedBy(); + /// + /// Returns an over the `Termination queue` set + /// + /// + IEnumerable Getterminated(); + /// + /// Returns the top level receive behavior from the behavior stack + /// + /// + Receive GetCurrentBehavior(); + } + + /// + /// Represents the default start up state for any actor. + /// This state provides capacity for one `WatchedBy` and one `Receive` behavior + /// As soon as this container is no longer enough to contain the current state + /// The state container will escalate into a `FullActorState` instance + /// + internal class DefaultActorState : IActorState + { + private IActorRef _watchedBy; + private Receive _receive; + public IActorState RemoveWatching(IActorRef actor) + { + return this; + } + + public IActorState RemoveWatchedBy(IActorRef actor) + { + return this; + } + + public IActorState RemoveTerminated(IActorRef actor) + { + return this; + } + + public IActorState AddWatching(IActorRef actor) + { + return GetFullState().AddWatching(actor); + } + + public IActorState AddWatchedBy(IActorRef actor) + { + //if we have no watchedBy, assign it to our local ref + //this is a memory footprint optimization, we can have a DefaultActorState object that is watched by _one_ watcher (parent) + //in every other case, we escalate to FullActorState + if (_watchedBy == null) + { + _watchedBy = actor; + return this; + } + //otherwise, add our existing watchedBy and the new actor to the new fullstate container + return GetFullState().AddWatchedBy(actor); + + } + + private FullActorState GetFullState() + { + var res = new FullActorState(); + if (_receive != null) + res.Become(_receive); + + if (_watchedBy != null) + res.AddWatchedBy(_watchedBy); + + return res; + } + + public IActorState AddTerminated(IActorRef actor) + { + return GetFullState().AddTerminated(actor); + } + + public bool ContainsWatching(IActorRef actor) + { + return false; + } + + public bool ContainsWatchedBy(IActorRef actor) + { + if (_watchedBy == null) + return false; + + return _watchedBy.Equals(actor); + } + + public bool ContainsTerminated(IActorRef actor) + { + return false; + } + + public IEnumerable GetWatching() + { + yield break; + } + + public IEnumerable GetWatchedBy() + { + if (_watchedBy != null) + yield return _watchedBy; + } + + public IEnumerable Getterminated() + { + yield break; + } + + public IActorState ClearWatching() + { + return this; + } + + public IActorState ClearTerminated() + { + return this; + } + + public IActorState Become(Receive receive) + { + if (_receive == null) + { + _receive = receive; + return this; + } + return GetFullState().BecomeStacked(receive); + } + + public IActorState BecomeStacked(Receive receive) + { + if (_receive == null) + { + _receive = receive; + return this; + } + return GetFullState().BecomeStacked(receive); + } + + public IActorState UnbecomeStacked() + { + return this; + } + + + public IActorState ClearBehaviorStack() + { + _receive = null; + return this; + } + + + public Receive GetCurrentBehavior() + { + //TODO: throw if null? + return _receive; + } + } + + /// + /// Represents the full state of an actor, this is used whenever an actor need more state than the `DefaultActorState` container can contain + /// + internal class FullActorState : IActorState + { + private readonly HashSet _watching = new HashSet(); + private readonly HashSet _watchedBy = new HashSet(); + private readonly HashSet _terminatedQueue = new HashSet();//terminatedqueue should never be used outside the message loop + private Stack _behaviorStack = new Stack(2); + public IActorState RemoveWatching(IActorRef actor) + { + _watching.Remove(actor); + return this; + } + + public IActorState RemoveWatchedBy(IActorRef actor) + { + _watchedBy.Remove(actor); + return this; + } + + public IActorState RemoveTerminated(IActorRef actor) + { + _terminatedQueue.Remove(actor); + return this; + } + + public IActorState AddWatching(IActorRef actor) + { + _watching.Add(actor); + return this; + } + + public IActorState AddWatchedBy(IActorRef actor) + { + _watchedBy.Add(actor); + return this; + } + + public IActorState AddTerminated(IActorRef actor) + { + _terminatedQueue.Add(actor); + return this; + } + + + public bool ContainsWatching(IActorRef actor) + { + return _watching.Contains(actor); + } + + public bool ContainsWatchedBy(IActorRef actor) + { + return _watchedBy.Contains(actor); + } + + public bool ContainsTerminated(IActorRef actor) + { + return _terminatedQueue.Contains(actor); + } + + public IEnumerable GetWatching() + { + return _watching; + } + + public IEnumerable GetWatchedBy() + { + return _watchedBy; + } + + public IEnumerable Getterminated() + { + return _terminatedQueue; + } + + + public IActorState ClearWatching() + { + _watching.Clear(); + return this; + } + + + public IActorState ClearTerminated() + { + _terminatedQueue.Clear(); + return this; + } + + + public IActorState Become(Receive receive) + { + if (_behaviorStack.Count > 1) //We should never pop off the initial receiver + _behaviorStack.Pop(); + _behaviorStack.Push(receive); + return this; + } + + public IActorState BecomeStacked(Receive receive) + { + _behaviorStack.Push(receive); + return this; + } + + public IActorState UnbecomeStacked() + { + if (_behaviorStack.Count > 1) //We should never pop off the initial receiver + _behaviorStack.Pop(); + + return this; + } + + public IActorState ClearBehaviorStack() + { + _behaviorStack = new Stack(1); + return this; + } + + + public Receive GetCurrentBehavior() + { + return _behaviorStack.Peek(); + } + } +} diff --git a/src/core/Akka/Akka.csproj b/src/core/Akka/Akka.csproj index b19a1794f9b..e3a9dd6175e 100644 --- a/src/core/Akka/Akka.csproj +++ b/src/core/Akka/Akka.csproj @@ -57,10 +57,7 @@ - - - ..\..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll @@ -70,6 +67,7 @@ Properties\SharedAssemblyInfo.cs + @@ -201,6 +199,7 @@ + @@ -246,7 +245,9 @@ + + diff --git a/src/core/Akka/Akka.nuspec b/src/core/Akka/Akka.nuspec index c7ae21126f0..0a98c213420 100644 --- a/src/core/Akka/Akka.nuspec +++ b/src/core/Akka/Akka.nuspec @@ -9,7 +9,7 @@ Akka.NET is a port of the popular Java/Scala framework Akka to .NET https://github.com/akkadotnet/akka.net/blob/master/LICENSE https://github.com/akkadotnet/akka.net - https://raw.githubusercontent.com/akkadotnet/akka.net/gh-pages/images/icon-32x32.png + http://getakka.net/images/AkkaNetLogo.Normal.png false @releaseNotes@ @copyright@ @@ -17,4 +17,4 @@ @dependencies@ @references@ - \ No newline at end of file + diff --git a/src/core/Akka/Dispatch/AbstractDispatcher.cs b/src/core/Akka/Dispatch/AbstractDispatcher.cs index aea9102e71e..49bb5a429d9 100644 --- a/src/core/Akka/Dispatch/AbstractDispatcher.cs +++ b/src/core/Akka/Dispatch/AbstractDispatcher.cs @@ -147,27 +147,6 @@ public override MessageDispatcher Dispatcher() } } - /// - /// Used to create instances of the . - /// - /// Always returns the same instance. - /// - /// - class PinnedDispatcherConfigurator : MessageDispatcherConfigurator - { - public PinnedDispatcherConfigurator(Config config, IDispatcherPrerequisites prerequisites) : base(config, prerequisites) - { - _dispatcher = new SingleThreadDispatcher(this); - } - - private readonly SingleThreadDispatcher _dispatcher; - - public override MessageDispatcher Dispatcher() - { - return _dispatcher; - } - } - /// /// Used to create instances of the . /// @@ -188,25 +167,8 @@ public override MessageDispatcher Dispatcher() } /// - /// Lookup list for different types of out-of-the-box s. - /// - public enum DispatcherType - { - Dispatcher, - TaskDispatcher, - PinnedDispatcher, - SynchronizedDispatcher, - } - public static class DispatcherTypeMembers - { - public static string GetName(this DispatcherType self) - { - //TODO: switch case return string? - return self.ToString(); - } - } - /// - /// Class MessageDispatcher. + /// Class responsible for pushing messages from an actor's mailbox into its + /// receive methods. Comes in many different flavors. /// public abstract class MessageDispatcher { @@ -267,6 +229,33 @@ public virtual void SystemDispatch(ActorCell cell, Envelope envelope) { cell.SystemInvoke(envelope); } + + /// + /// Attaches the dispatcher to the + /// + /// + /// Practically, doesn't do very much right now - dispatchers aren't responsible for creating + /// mailboxes in Akka.NET + /// + /// + /// The ActorCell belonging to the actor who's attaching to this dispatcher. + public virtual void Attach(ActorCell cell) + { + + } + + /// + /// Detaches the dispatcher to the + /// + /// + /// Only really used in dispatchers with 1:1 relationship with dispatcher. + /// + /// + /// The ActorCell belonging to the actor who's deatching from this dispatcher. + public virtual void Detach(ActorCell cell) + { + + } } } diff --git a/src/core/Akka/Dispatch/ActorTaskScheduler.cs b/src/core/Akka/Dispatch/ActorTaskScheduler.cs index c002e131b2a..cc94ed6f18e 100644 --- a/src/core/Akka/Dispatch/ActorTaskScheduler.cs +++ b/src/core/Akka/Dispatch/ActorTaskScheduler.cs @@ -53,7 +53,8 @@ protected override IEnumerable GetScheduledTasks() protected override void QueueTask(Task task) { - if (task.AsyncState == Outer) + var s = CallContext.LogicalGetData(StateKey) as AmbientState; + if (task.AsyncState == Outer || s == null) { TryExecuteTask(task); return; @@ -61,10 +62,11 @@ protected override void QueueTask(Task task) //we get here if the task needs to be marshalled back to the mailbox //e.g. if previous task was an IO completion - var s = CallContext.LogicalGetData(StateKey) as AmbientState; + s = CallContext.LogicalGetData(StateKey) as AmbientState; s.Self.Tell(new CompleteTask(s, () => { + SetCurrentState(s.Self,s.Sender,s.Message); TryExecuteTask(task); if (task.IsFaulted) Rethrow(task, null); @@ -130,7 +132,7 @@ await action() .ContinueWith( Rethrow, Faulted, - TaskContinuationOptions.OnlyOnFaulted); + TaskContinuationOptions.None); //if reentrancy was suspended, make sure we re-enable message processing again if (behavior == AsyncBehavior.Suspend) diff --git a/src/core/Akka/Dispatch/CachingConfig.cs b/src/core/Akka/Dispatch/CachingConfig.cs index d5558dbafc0..cdfd8ab1046 100644 --- a/src/core/Akka/Dispatch/CachingConfig.cs +++ b/src/core/Akka/Dispatch/CachingConfig.cs @@ -5,11 +5,6 @@ // //----------------------------------------------------------------------- -/** - * Copyright (C) 2009-2015 Typesafe Inc. - * Original C# code written by Akka.NET project - */ - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -125,7 +120,7 @@ private IPathEntry GetPathEntry(string path) pathEntry = new ValuePathEntry(true, true, configValue.AtKey("cached")); } } - catch (Exception ex) + catch (Exception) { pathEntry = EmptyPathEntry; } @@ -135,7 +130,7 @@ private IPathEntry GetPathEntry(string path) pathEntry = NonExistingPathEntry; } } - catch (Exception ex) //configuration threw some sort of error + catch (Exception) //configuration threw some sort of error { pathEntry = InvalidPathEntry; } diff --git a/src/core/Akka/Dispatch/ConcurrentQueueMailbox.cs b/src/core/Akka/Dispatch/ConcurrentQueueMailbox.cs index 3330e8f784b..f493e2fc651 100644 --- a/src/core/Akka/Dispatch/ConcurrentQueueMailbox.cs +++ b/src/core/Akka/Dispatch/ConcurrentQueueMailbox.cs @@ -91,7 +91,7 @@ private void Run() //if deadline time have expired, stop and break if (throughputDeadlineTime.HasValue && throughputDeadlineTime.Value > 0 && - _deadLineTimer.ElapsedTicks > throughputDeadlineTime.Value) + _deadLineTimer.Elapsed.Ticks > throughputDeadlineTime.Value) { _deadLineTimer.Stop(); break; diff --git a/src/core/Akka/Dispatch/DequeBasedMailbox.cs b/src/core/Akka/Dispatch/DequeBasedMailbox.cs index 797ae7c8887..8a564ab37ae 100644 --- a/src/core/Akka/Dispatch/DequeBasedMailbox.cs +++ b/src/core/Akka/Dispatch/DequeBasedMailbox.cs @@ -6,7 +6,6 @@ //----------------------------------------------------------------------- using Akka.Actor; -using Akka.Dispatch.MessageQueues; namespace Akka.Dispatch { diff --git a/src/core/Akka/Dispatch/DispatcherConfigurator.cs b/src/core/Akka/Dispatch/DispatcherConfigurator.cs index f6fc14e6986..584d1b00c3b 100644 --- a/src/core/Akka/Dispatch/DispatcherConfigurator.cs +++ b/src/core/Akka/Dispatch/DispatcherConfigurator.cs @@ -5,11 +5,6 @@ // //----------------------------------------------------------------------- -/** - * Copyright (C) 2009-2015 Typesafe Inc. - * Original C# code written by Akka.NET project - */ - namespace Akka.Dispatch { diff --git a/src/core/Akka/Dispatch/Dispatchers.cs b/src/core/Akka/Dispatch/Dispatchers.cs index 2da92e128e6..bde518643fc 100644 --- a/src/core/Akka/Dispatch/Dispatchers.cs +++ b/src/core/Akka/Dispatch/Dispatchers.cs @@ -5,10 +5,6 @@ // //----------------------------------------------------------------------- -/** - * Copyright (C) 2009-2015 Typesafe Inc. - * Original C# code written by Akka.NET project - */ using System; using System.Collections.Concurrent; using System.Threading; @@ -50,6 +46,9 @@ public static Task ScheduleAsync(this MessageDispatcher dispatcher, Func public class ThreadPoolDispatcher : MessageDispatcher { + + private static readonly bool _isFullTrusted = AppDomain.CurrentDomain.IsFullyTrusted; + /// /// Takes a /// @@ -64,8 +63,11 @@ public ThreadPoolDispatcher(MessageDispatcherConfigurator configurator) : base(c public override void Schedule(Action run) { var wc = new WaitCallback(_ => run()); - ThreadPool.UnsafeQueueUserWorkItem(wc, null); - //ThreadPool.QueueUserWorkItem(wc, null); + // we use unsafe version if current application domain is FullTrusted + if (_isFullTrusted) + ThreadPool.UnsafeQueueUserWorkItem(wc, null); + else + ThreadPool.QueueUserWorkItem(wc, null); } } @@ -99,48 +101,6 @@ public override void Schedule(Action run) } } - /// - /// Class SingleThreadDispatcher. - /// - public class SingleThreadDispatcher : MessageDispatcher - { - /// - /// The queue - /// - private readonly BlockingCollection queue = new BlockingCollection(); - - /// - /// The running - /// - private volatile bool running = true; - - /// - /// Initializes a new instance of the class. - /// - public SingleThreadDispatcher(MessageDispatcherConfigurator configurator) - : base(configurator) - { - var thread = new Thread(_ => - { - foreach (var next in queue.GetConsumingEnumerable()) - { - next(); - if (!running) return; - } - }); - thread.Start(); //thread won't start automatically without this - } - - /// - /// Schedules the specified run. - /// - /// The run. - public override void Schedule(Action run) - { - queue.Add(run); - } - } - /// /// The registry of all instances available to this . /// diff --git a/src/core/Akka/Dispatch/ForkJoinDispatcher.cs b/src/core/Akka/Dispatch/ForkJoinDispatcher.cs index 5c895098633..f31c26e8110 100644 --- a/src/core/Akka/Dispatch/ForkJoinDispatcher.cs +++ b/src/core/Akka/Dispatch/ForkJoinDispatcher.cs @@ -23,11 +23,11 @@ public class ForkJoinDispatcherConfigurator : MessageDispatcherConfigurator public ForkJoinDispatcherConfigurator(Config config, IDispatcherPrerequisites prerequisites) : base(config, prerequisites) { var dtp = config.GetConfig("dedicated-thread-pool"); - if(dtp.IsEmpty) throw new ConfigurationException(string.Format("must define section dedicated-thread-pool for ForkJoinDispatcher {0}", config.GetString("id", "unknown"))); + if (dtp == null || dtp.IsEmpty) throw new ConfigurationException(string.Format("must define section dedicated-thread-pool for ForkJoinDispatcher {0}", config.GetString("id", "unknown"))); - var settings = new DedicatedThreadPoolSettings(dtp.GetInt("thread-count"), - ConfigureThreadType(dtp.GetString("threadtype", ThreadType.Background.ToString())), - GetSafeDeadlockTimeout(dtp)); + var settings = new DedicatedThreadPoolSettings(dtp.GetInt("thread-count"), + DedicatedThreadPoolConfigHelpers.ConfigureThreadType(dtp.GetString("threadtype", ThreadType.Background.ToString())), + DedicatedThreadPoolConfigHelpers.GetSafeDeadlockTimeout(dtp)); _instance = new ForkJoinDispatcher(this, settings); } @@ -37,20 +37,6 @@ public override MessageDispatcher Dispatcher() { return _instance; } - - private static TimeSpan? GetSafeDeadlockTimeout(Config cfg) - { - var timespan = cfg.GetTimeSpan("deadlock-timeout", TimeSpan.FromSeconds(-1)); - if (timespan.TotalSeconds < 0) - return null; - return timespan; - } - - private static ThreadType ConfigureThreadType(string threadType) - { - return string.Compare(threadType, ThreadType.Foreground.ToString(), StringComparison.InvariantCultureIgnoreCase) == 0 ? - ThreadType.Foreground : ThreadType.Background; - } } /// diff --git a/src/core/Akka/Dispatch/GenericMailbox.cs b/src/core/Akka/Dispatch/GenericMailbox.cs index 1292f93a4a0..cb199144922 100644 --- a/src/core/Akka/Dispatch/GenericMailbox.cs +++ b/src/core/Akka/Dispatch/GenericMailbox.cs @@ -112,7 +112,7 @@ private void Run() //if deadline time have expired, stop and break if (throughputDeadlineTime.HasValue && throughputDeadlineTime.Value > 0 && - _deadLineTimer.ElapsedTicks > throughputDeadlineTime.Value) + _deadLineTimer.Elapsed.Ticks > throughputDeadlineTime.Value) { _deadLineTimer.Stop(); break; diff --git a/src/core/Akka/Dispatch/SingleThreadDispatcher.cs b/src/core/Akka/Dispatch/SingleThreadDispatcher.cs new file mode 100644 index 00000000000..9a207205e8b --- /dev/null +++ b/src/core/Akka/Dispatch/SingleThreadDispatcher.cs @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2015 Typesafe Inc. +// Copyright (C) 2013-2015 Akka.NET project +// +//----------------------------------------------------------------------- + +using System; +using Akka.Actor; +using Akka.Configuration; +using Helios.Concurrency; + +namespace Akka.Dispatch +{ + /// + /// Used to create instances of the . + /// + /// Each actor created using the pinned dispatcher gets its own unique thread. + /// + /// Always returns a new instance. + /// + /// + class PinnedDispatcherConfigurator : MessageDispatcherConfigurator + { + private readonly DedicatedThreadPoolSettings _settings; + + public PinnedDispatcherConfigurator(Config config, IDispatcherPrerequisites prerequisites) + : base(config, prerequisites) + { + var dtp = config.GetConfig("dedicated-thread-pool"); + if (dtp == null || dtp.IsEmpty) + { + _settings = DedicatedThreadPoolConfigHelpers.DefaultSingleThreadPoolSettings; + } + else + { + _settings = new DedicatedThreadPoolSettings(1, + DedicatedThreadPoolConfigHelpers.ConfigureThreadType(dtp.GetString("threadtype", ThreadType.Background.ToString())), + DedicatedThreadPoolConfigHelpers.GetSafeDeadlockTimeout(dtp)); + } + } + + public override MessageDispatcher Dispatcher() + { + return new SingleThreadDispatcher(this, _settings); + } + } + + + /// + /// Used to power the . + /// + /// Guaranteed to provide one new thread instance per actor. + /// + /// Uses with 1 thread in order + /// to take advantage of standard cleanup / teardown / queueing mechanics. + /// + /// /// Relevant configuration options: + /// + /// my-forkjoin-dispatcher{ + /// type = PinnedDispatcher + /// throughput = 100 + /// dedicated-thread-pool{ #settings for Helios.DedicatedThreadPool + /// #deadlock-timeout = 3s #optional timeout for deadlock detection + /// threadtype = background #values can be "background" or "foreground" + /// } + /// } + /// + /// my-other-forkjoin-dispatcher{ + /// type = PinnedDispatcher + /// # dedicated-thread-pool section is optional + /// } + /// + /// + /// Worth noting that unlike the , the + /// does not respect the dedicated-thread-pool.thread-count property in configuration. That value is + /// always equal to 1 in the . + /// + /// + public class SingleThreadDispatcher : MessageDispatcher + { + private readonly DedicatedThreadPool _dedicatedThreadPool; + + internal SingleThreadDispatcher(MessageDispatcherConfigurator configurator, DedicatedThreadPoolSettings settings) + : base(configurator) + { + _dedicatedThreadPool = new DedicatedThreadPool(settings); + } + + /// + /// Schedules the specified run. + /// + /// The run. + public override void Schedule(Action run) + { + _dedicatedThreadPool.QueueUserWorkItem(run); + } + + public override void Detach(ActorCell cell) + { + //shut down the dedicated thread pool + _dedicatedThreadPool.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/core/Akka/Dispatch/ThreadPoolBuilder.cs b/src/core/Akka/Dispatch/ThreadPoolBuilder.cs index 0ca87fb94fa..b23c84c747c 100644 --- a/src/core/Akka/Dispatch/ThreadPoolBuilder.cs +++ b/src/core/Akka/Dispatch/ThreadPoolBuilder.cs @@ -7,11 +7,34 @@ using System; using Akka.Configuration; +using Helios.Concurrency; namespace Akka.Dispatch { - class ThreadPoolBuilder + /// + /// helper class for configuring + /// instances who depend on the Helios . + /// + internal static class DedicatedThreadPoolConfigHelpers { + internal static TimeSpan? GetSafeDeadlockTimeout(Config cfg) + { + var timespan = cfg.GetTimeSpan("deadlock-timeout", TimeSpan.FromSeconds(-1)); + if (timespan.TotalSeconds < 0) + return null; + return timespan; + } + + internal static ThreadType ConfigureThreadType(string threadType) + { + return string.Compare(threadType, ThreadType.Foreground.ToString(), StringComparison.InvariantCultureIgnoreCase) == 0 ? + ThreadType.Foreground : ThreadType.Background; + } + + /// + /// Default settings for instances. + /// + internal static readonly DedicatedThreadPoolSettings DefaultSingleThreadPoolSettings = new DedicatedThreadPoolSettings(1); } /// diff --git a/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs b/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs index 16cd82f89d1..c475314c215 100644 --- a/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs +++ b/src/core/Akka/Helios.Concurrency.DedicatedThreadPool.cs @@ -1,14 +1,8 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2015 Typesafe Inc. -// Copyright (C) 2013-2015 Akka.NET project -// -//----------------------------------------------------------------------- - -/* +/* * Copyright 2015 Roger Alsing, Aaron Stannard * Helios.DedicatedThreadPool - https://github.com/helios-io/DedicatedThreadPool */ + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -419,7 +413,7 @@ public PoolWorker(WorkerQueue work, DedicatedThreadPool pool, bool errorRecovery if (_pool.ShutdownRequested) return; action(); } - catch (Exception ex) + catch (Exception) { Failover(true); return; diff --git a/src/core/Akka/Routing/RoundRobin.cs b/src/core/Akka/Routing/RoundRobin.cs index c153738f169..9120204e56e 100644 --- a/src/core/Akka/Routing/RoundRobin.cs +++ b/src/core/Akka/Routing/RoundRobin.cs @@ -9,7 +9,6 @@ using System.Threading; using Akka.Actor; using Akka.Configuration; -using Akka.Dispatch; using Akka.Util; namespace Akka.Routing @@ -19,10 +18,18 @@ namespace Akka.Routing /// public class RoundRobinRoutingLogic : RoutingLogic { + + public RoundRobinRoutingLogic() : this(-1) {} + + public RoundRobinRoutingLogic(int next) + { + _next = next; + } + /// /// The next /// - private int _next = -1; + private int _next; /// /// Selects the specified message. @@ -36,7 +43,7 @@ public override Routee Select(object message, Routee[] routees) { return Routee.NoRoutee; } - return routees[Interlocked.Increment(ref _next)%routees.Length]; + return routees[(Interlocked.Increment(ref _next) & int.MaxValue) % routees.Length]; } } diff --git a/src/core/Akka/Routing/ScatterGatherFirstCompleted.cs b/src/core/Akka/Routing/ScatterGatherFirstCompleted.cs index 1062c6c1ef2..bed6e7462b9 100644 --- a/src/core/Akka/Routing/ScatterGatherFirstCompleted.cs +++ b/src/core/Akka/Routing/ScatterGatherFirstCompleted.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Akka.Actor; using Akka.Configuration; -using Akka.Dispatch; using Akka.Util; namespace Akka.Routing diff --git a/src/core/Akka/Routing/SmallestMailbox.cs b/src/core/Akka/Routing/SmallestMailbox.cs index a393a02fcb2..e7ae1e8c344 100644 --- a/src/core/Akka/Routing/SmallestMailbox.cs +++ b/src/core/Akka/Routing/SmallestMailbox.cs @@ -14,7 +14,15 @@ namespace Akka.Routing { public class SmallestMailboxRoutingLogic : RoutingLogic { - private int next = -1; + + public SmallestMailboxRoutingLogic() {} + + public SmallestMailboxRoutingLogic(int next) + { + _next = next; + } + + private int _next; public override Routee Select(object message, Routee[] routees) { @@ -35,7 +43,7 @@ private Routee SelectNext(Routee[] routees) var winningScore = long.MaxValue; // round robin fallback - var winner = routees[Interlocked.Increment(ref next) % routees.Length]; + var winner = routees[(Interlocked.Increment(ref _next) & int.MaxValue) % routees.Length]; for (int i = 0; i < routees.Length; i++) { diff --git a/src/core/Akka/Routing/TailChoppingRoutingLogic.cs b/src/core/Akka/Routing/TailChoppingRoutingLogic.cs index ebf1787a7d9..d3583c81c6e 100644 --- a/src/core/Akka/Routing/TailChoppingRoutingLogic.cs +++ b/src/core/Akka/Routing/TailChoppingRoutingLogic.cs @@ -119,27 +119,36 @@ public override void Send(object message, IActorRef sender) var completion = new TaskCompletionSource(); var cancelable = new Cancelable(_scheduler); - _scheduler.Advanced.ScheduleRepeatedly(TimeSpan.Zero, _interval, async () => - { - var currentIndex = routeeIndex.GetAndIncrement(); - if(currentIndex < _routees.Length) - { - completion.TrySetResult(await ((Task)_routees[currentIndex].Ask(message, null))); - } - }, cancelable); + completion.Task + .ContinueWith(task => cancelable.Cancel(false)); - _scheduler.Advanced.ScheduleOnce(_within, () => + if (_routees.Length == 0) { - completion.TrySetException(new TimeoutException(String.Format("Ask timed out on {0} after {1}", sender, _within))); - }, cancelable); - - var request = completion.Task; - completion.Task.ContinueWith(task => + completion.TrySetResult(NoRoutee); + } + else { - cancelable.Cancel(false); - }); + _scheduler.Advanced.ScheduleRepeatedly(TimeSpan.Zero, _interval, async () => + { + var currentIndex = routeeIndex.GetAndIncrement(); + if (currentIndex >= _routees.Length) + return; + + try + { + + completion.TrySetResult(await ((Task)_routees[currentIndex].Ask(message, _within))); + } + catch (TaskCanceledException) + { + completion.TrySetResult( + new Status.Failure( + new TimeoutException(String.Format("Ask timed out on {0} after {1}", sender, _within)))); + } + }, cancelable); + } - request.PipeTo(sender); + completion.Task.PipeTo(sender); } } diff --git a/src/core/Akka/Util/AtomicBoolean.cs b/src/core/Akka/Util/AtomicBoolean.cs index 3c3e33dd2b5..082586b7029 100644 --- a/src/core/Akka/Util/AtomicBoolean.cs +++ b/src/core/Akka/Util/AtomicBoolean.cs @@ -49,9 +49,8 @@ public bool Value /// /// If equals , then set the Value to /// . - /// - /// Returns true if was set, false otherise. /// + /// true if was set public bool CompareAndSet(bool expected, bool newValue) { var expectedInt = expected ? _trueValue : _falseValue; diff --git a/src/core/Akka/Util/AtomicReference.cs b/src/core/Akka/Util/AtomicReference.cs index b58748c3b1b..3c4cd76e346 100644 --- a/src/core/Akka/Util/AtomicReference.cs +++ b/src/core/Akka/Util/AtomicReference.cs @@ -58,9 +58,8 @@ public T Value /// /// If equals , then set the Value to /// . - /// - /// Returns true if was set, false otherise. /// + /// true if was set public bool CompareAndSet(T expected, T newValue) { //special handling for null values diff --git a/src/core/Akka/Util/Internal/TaskExtensions.cs b/src/core/Akka/Util/Internal/TaskExtensions.cs new file mode 100644 index 00000000000..94681eb3b41 --- /dev/null +++ b/src/core/Akka/Util/Internal/TaskExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Akka.Util.Internal +{ + public static class TaskExtensions + { + public static Task CastTask(this Task task) + { + if (task.IsCompleted) + return Task.FromResult((TResult) (object)task.Result); + var tcs = new TaskCompletionSource(); + if (task.IsFaulted) + tcs.SetException(task.Exception); + else + task.ContinueWith(_ => + { + if (task.IsFaulted || task.Exception != null) + tcs.SetException(task.Exception); + else if (task.IsCanceled) + tcs.SetCanceled(); + else + try + { + tcs.SetResult((TResult) (object) task.Result); + } + catch (Exception e) + { + tcs.SetException(e); + } + }, TaskContinuationOptions.ExecuteSynchronously); + return tcs.Task; + } + } +} diff --git a/src/core/Akka/Util/ListPriorityQueue.cs b/src/core/Akka/Util/ListPriorityQueue.cs index 9dee9571e0d..538ed354c82 100644 --- a/src/core/Akka/Util/ListPriorityQueue.cs +++ b/src/core/Akka/Util/ListPriorityQueue.cs @@ -61,7 +61,7 @@ public Envelope Dequeue() var ci = pi * 2 + 1; // left child index of parent if (ci > li) break; // no children so done var rc = ci + 1; // right child - if (rc <= li && _priorityCalculator(_data[rc]).CompareTo(_priorityCalculator(_data[ci])) < 0) // if there is a rc (ci + 1), and it is smaller than left child, use the rc instead + if (rc <= li && _priorityCalculator(_data[rc].Message).CompareTo(_priorityCalculator(_data[ci].Message)) < 0) // if there is a rc (ci + 1), and it is smaller than left child, use the rc instead ci = rc; if (_priorityCalculator(_data[pi].Message).CompareTo(_priorityCalculator(_data[ci].Message)) <= 0) break; // parent is smaller than (or equal to) smallest child so done var tmp = _data[pi]; _data[pi] = _data[ci]; _data[ci] = tmp; // swap parent and child diff --git a/src/core/Akka/Util/MatchHandler/MatchExpressionBuilder.cs b/src/core/Akka/Util/MatchHandler/MatchExpressionBuilder.cs index 42ba0044911..27ea7542058 100644 --- a/src/core/Akka/Util/MatchHandler/MatchExpressionBuilder.cs +++ b/src/core/Akka/Util/MatchHandler/MatchExpressionBuilder.cs @@ -135,7 +135,7 @@ public MatchExpressionBuilderResult BuildLambdaExpression(IReadOnlyList DecorateHandlerAndPredicateExpressions(IReadOnlyList arguments, ParameterExpression inputParameter) { //Warning: This is using the same algorithm as CreateArgumentValuesArray. - // Any updates in this shoul be made in CreateArgumentValuesArray as well. + // Any updates in this should be made in CreateArgumentValuesArray as well. // //If we only have a few arguments, the parameters will be: // (arguments_0, arguments_1) diff --git a/src/core/Akka/Util/MonotonicClock.cs b/src/core/Akka/Util/MonotonicClock.cs new file mode 100644 index 00000000000..54857f4e5a9 --- /dev/null +++ b/src/core/Akka/Util/MonotonicClock.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Akka.Util +{ + internal static class MonotonicClock + { + private static readonly Stopwatch Stopwatch = Stopwatch.StartNew(); + private static readonly bool IsMono = Type.GetType("Mono.Runtime") != null; + [DllImport("kernel32")] + private static extern ulong GetTickCount64(); + + private const int TicksInMillisecond = 10000; + + public static TimeSpan Elapsed + { + get + { + return IsMono + ? Stopwatch.Elapsed + : new TimeSpan((long) GetTickCount64()*TicksInMillisecond); + } + } + + public static TimeSpan ElapsedHighRes + { + get { return Stopwatch.Elapsed; } + } + } +} diff --git a/src/examples/Chat/ChatClient/ChatClient.csproj b/src/examples/Chat/ChatClient/ChatClient.csproj index 041709f5a98..b79acc0c1f7 100644 --- a/src/examples/Chat/ChatClient/ChatClient.csproj +++ b/src/examples/Chat/ChatClient/ChatClient.csproj @@ -57,11 +57,7 @@ - - - - diff --git a/src/examples/Chat/ChatMessages/ChatMessages.csproj b/src/examples/Chat/ChatMessages/ChatMessages.csproj index e36454e6538..1f9fcddc860 100644 --- a/src/examples/Chat/ChatMessages/ChatMessages.csproj +++ b/src/examples/Chat/ChatMessages/ChatMessages.csproj @@ -56,11 +56,7 @@ - - - - ..\..\..\packages\fastJSON.2.0.27.1\lib\net40\fastjson.dll diff --git a/src/examples/Chat/ChatServer/ChatServer.csproj b/src/examples/Chat/ChatServer/ChatServer.csproj index 4ebeeee005b..76b9e386a63 100644 --- a/src/examples/Chat/ChatServer/ChatServer.csproj +++ b/src/examples/Chat/ChatServer/ChatServer.csproj @@ -57,11 +57,7 @@ - - - - diff --git a/src/examples/Chat/ChatServer/Program.cs b/src/examples/Chat/ChatServer/Program.cs index 59ff260f071..7491defdbb1 100644 --- a/src/examples/Chat/ChatServer/Program.cs +++ b/src/examples/Chat/ChatServer/Program.cs @@ -5,13 +5,11 @@ // //----------------------------------------------------------------------- -using ChatMessages; -using Akka; -using Akka.Actor; using System; using System.Collections.Generic; +using Akka.Actor; using Akka.Configuration; -using Akka.Event; +using ChatMessages; namespace ChatServer { @@ -27,10 +25,10 @@ static void Main(string[] args) remote { helios.tcp { transport-class = ""Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote"" - applied-adapters = [] - transport-protocol = tcp - port = 8081 - hostname = localhost + applied-adapters = [] + transport-protocol = tcp + port = 8081 + hostname = localhost } } } diff --git a/src/examples/Cluster/Roles/Samples.Cluster.Transformation/Samples.Cluster.Transformation.csproj b/src/examples/Cluster/Roles/Samples.Cluster.Transformation/Samples.Cluster.Transformation.csproj index 41f92d89226..49d217bedbe 100644 --- a/src/examples/Cluster/Roles/Samples.Cluster.Transformation/Samples.Cluster.Transformation.csproj +++ b/src/examples/Cluster/Roles/Samples.Cluster.Transformation/Samples.Cluster.Transformation.csproj @@ -40,11 +40,7 @@ - - - - diff --git a/src/examples/Cluster/Routing/Samples.Cluster.ConsistentHashRouting/Samples.Cluster.ConsistentHashRouting.csproj b/src/examples/Cluster/Routing/Samples.Cluster.ConsistentHashRouting/Samples.Cluster.ConsistentHashRouting.csproj index 617548d9314..a1067e6b236 100644 --- a/src/examples/Cluster/Routing/Samples.Cluster.ConsistentHashRouting/Samples.Cluster.ConsistentHashRouting.csproj +++ b/src/examples/Cluster/Routing/Samples.Cluster.ConsistentHashRouting/Samples.Cluster.ConsistentHashRouting.csproj @@ -40,11 +40,7 @@ - - - - diff --git a/src/examples/Cluster/Samples.Cluster.Simple/Samples.Cluster.Simple.csproj b/src/examples/Cluster/Samples.Cluster.Simple/Samples.Cluster.Simple.csproj index ca525c07870..8582dee911a 100644 --- a/src/examples/Cluster/Samples.Cluster.Simple/Samples.Cluster.Simple.csproj +++ b/src/examples/Cluster/Samples.Cluster.Simple/Samples.Cluster.Simple.csproj @@ -40,11 +40,7 @@ - - - - diff --git a/src/examples/HelloWorld/HelloAkka/HelloAkka.csproj b/src/examples/HelloWorld/HelloAkka/HelloAkka.csproj index 5cf3586ebff..fc80a3402ff 100644 --- a/src/examples/HelloWorld/HelloAkka/HelloAkka.csproj +++ b/src/examples/HelloWorld/HelloAkka/HelloAkka.csproj @@ -34,11 +34,7 @@ - - - - diff --git a/src/examples/PersistenceExample/ExamplePersistentActor.cs b/src/examples/PersistenceExample/ExamplePersistentActor.cs index 74d1a723e10..1c3f930a348 100644 --- a/src/examples/PersistenceExample/ExamplePersistentActor.cs +++ b/src/examples/PersistenceExample/ExamplePersistentActor.cs @@ -99,9 +99,9 @@ protected override bool ReceiveCommand(object message) var cmd = message as Command; Persist(new Event(cmd.Data + "-" + EventsCount), UpdateState); } - else if (message == "snap") + else if (message as string == "snap") SaveSnapshot(State); - else if (message == "print") + else if (message as string == "print") Console.WriteLine(State); else return false; return true; diff --git a/src/examples/PersistenceExample/ExamplePersistentFailingActor.cs b/src/examples/PersistenceExample/ExamplePersistentFailingActor.cs index 905e1e7cbe6..f0205d840a4 100644 --- a/src/examples/PersistenceExample/ExamplePersistentFailingActor.cs +++ b/src/examples/PersistenceExample/ExamplePersistentFailingActor.cs @@ -32,9 +32,9 @@ protected override bool ReceiveRecover(object message) protected override bool ReceiveCommand(object message) { - if (message == "print") + if (message as string == "print") Console.WriteLine("Received: " + string.Join(";, ", Enumerable.Reverse(Received))); - else if (message == "boom") + else if (message as string == "boom") throw new Exception("controlled demolition"); else if (message is string) Persist(message.ToString(), s => Received.AddFirst(s)); diff --git a/src/examples/PersistenceExample/ExampleView.cs b/src/examples/PersistenceExample/ExampleView.cs index 22f5a144438..aa72ee9dae6 100644 --- a/src/examples/PersistenceExample/ExampleView.cs +++ b/src/examples/PersistenceExample/ExampleView.cs @@ -19,7 +19,7 @@ public class ExampleView : PersistentView protected override bool Receive(object message) { - if (message == "snap") + if (message as string == "snap") { Console.WriteLine("View saving snapshot"); SaveSnapshot(_numReplicated); diff --git a/src/examples/PersistenceExample/GuaranteedDeliveryExampleActor.cs b/src/examples/PersistenceExample/GuaranteedDeliveryExampleActor.cs new file mode 100644 index 00000000000..5e862cb58fc --- /dev/null +++ b/src/examples/PersistenceExample/GuaranteedDeliveryExampleActor.cs @@ -0,0 +1,147 @@ +using System; +using Akka.Actor; +using Akka.Persistence; + +namespace PersistenceExample +{ + public class Message + { + public Message(string data) + { + this.Data = data; + } + + public string Data { get; private set; } + } + + public class Confirmable + { + public Confirmable(long deliveryId, string data) + { + this.DeliveryId = deliveryId; + this.Data = data; + } + + + public long DeliveryId { get; private set; } + + public string Data { get; private set; } + } + public class Confirmation + { + public Confirmation(long deliveryId) + { + this.DeliveryId = deliveryId; + } + + public long DeliveryId { get; private set; } + } + [Serializable] + public class Snap + { + public Snap(GuaranteedDeliverySnapshot snapshot) + { + this.Snapshot = snapshot; + } + + public GuaranteedDeliverySnapshot Snapshot { get; private set; } + } + + public class DeliveryActor : UntypedActor + { + bool Confirming = true; + + protected override void OnReceive(object message) + { + if (message as string == "start") + { + Confirming = true; + } + if (message as string == "stop") + { + Confirming = false; + } + if (message is Confirmable) + { + var msg = message as Confirmable; + if (Confirming) + { + Console.WriteLine("Confirming delivery of message id: {0} and data: {1}", msg.DeliveryId, msg.Data); + Context.Sender.Tell(new Confirmation(msg.DeliveryId)); + } + else + { + Console.WriteLine("Ignoring message id: {0} and data: {1}", msg.DeliveryId, msg.Data); + } + } + } + } + /// + /// GuaranteedDelivery will repeat sending messages, unless confirmed by deliveryId + /// + /// By default, in-memory Journal is used, so this won't survive system restarts. + /// + public class GuaranteedDeliveryExampleActor : GuaranteedDeliveryActor + { + public ActorPath DeliveryPath { get; private set; } + + public GuaranteedDeliveryExampleActor(ActorPath deliveryPath) + { + this.DeliveryPath = deliveryPath; + } + + public override string PersistenceId + { + get { return "guaranteed-1"; } + } + + protected override bool ReceiveRecover(object message) + { + if (message is Message) + { + var messageData = ((Message)message).Data; + Console.WriteLine("recovered {0}",messageData); + Deliver(DeliveryPath, + id => + { + Console.WriteLine("recovered delivery task: {0}, with deliveryId: {1}", messageData, id); + return new Confirmable(id, messageData); + }); + + } + else if (message is Confirmation) + { + var deliveryId = ((Confirmation)message).DeliveryId; + Console.WriteLine("recovered confirmation of {0}", deliveryId); + ConfirmDelivery(deliveryId); + } + else + return false; + return true; + } + + protected override bool ReceiveCommand(object message) + { + if (message as string == "boom") + throw new Exception("Controlled devastation"); + else if (message is Message) + { + Persist(message as Message, m => + { + Deliver(DeliveryPath, + id => + { + Console.WriteLine("sending: {0}, with deliveryId: {1}", m.Data, id); + return new Confirmable(id, m.Data); + }); + }); + } + else if (message is Confirmation) + { + Persist(message as Confirmation, m => ConfirmDelivery(m.DeliveryId)); + } + else return false; + return true; + } + } +} diff --git a/src/examples/PersistenceExample/PersistenceExample.csproj b/src/examples/PersistenceExample/PersistenceExample.csproj index 7f303310c38..ee14e7a6219 100644 --- a/src/examples/PersistenceExample/PersistenceExample.csproj +++ b/src/examples/PersistenceExample/PersistenceExample.csproj @@ -34,16 +34,15 @@ - - + @@ -53,6 +52,10 @@ + + {bac85686-afc4-413e-98dc-5ed8f468bc63} + Akka.Persistence.SqlServer + {FCA84DEA-C118-424B-9EB8-34375DFEF18A} Akka.Persistence diff --git a/src/examples/PersistenceExample/Program.cs b/src/examples/PersistenceExample/Program.cs index 61249f128a6..6882ec38f79 100644 --- a/src/examples/PersistenceExample/Program.cs +++ b/src/examples/PersistenceExample/Program.cs @@ -9,6 +9,7 @@ using Akka.Actor; using Akka.Configuration; using Akka.Persistence; +using Akka.Persistence.SqlServer; namespace PersistenceExample { @@ -16,23 +17,60 @@ class Program { static void Main(string[] args) { - var config = ConfigurationFactory.ParseString("akka.actor.logLevel = DEBUG") - .WithFallback(Persistence.DefaultConfig()); - - using (var system = ActorSystem.Create("example", config)) + var sqlServerConfig = ConfigurationFactory.ParseString(@" + akka.persistence.journal.plugin = ""akka.persistence.journal.sql-server"" + akka.persistence.journal.sql-server.connection-string = ""Data Source=.\\SQLEXPRESS;Initial Catalog=ExampleDb;Integrated Security=True"" + akka.persistence.journal.sql-server.auto-initialize = on + akka.persistence.snapshot-store.plugin = ""akka.persistence.snapshot-store.sql-server"" + akka.persistence.snapshot-store.sql-server.connection-string = ""Data Source=.\\SQLEXPRESS;Initial Catalog=ExampleDb;Integrated Security=True"" + akka.persistence.snapshot-store.sql-server.auto-initialize = on + "); + + using (var system = ActorSystem.Create("example")) { - //BasicUsage(system); + //SqlServerPersistence.Init(system); + BasicUsage(system); - // FailingActorExample(system); + //FailingActorExample(system); - SnapshotedActor(system); + //SnapshotedActor(system); //ViewExample(system); + GuaranteedDelivery(system); + Console.ReadLine(); } } + private static void GuaranteedDelivery(ActorSystem system) + { + Console.WriteLine("\n--- GUARANTEED DELIVERY EXAMPLE ---\n"); + var delivery = system.ActorOf(Props.Create(()=> new DeliveryActor()),"delivery"); + + var deliverer = system.ActorOf(Props.Create(() => new GuaranteedDeliveryExampleActor(delivery.Path))); + delivery.Tell("start"); + deliverer.Tell(new Message("foo")); + + + System.Threading.Thread.Sleep(1000); //making sure delivery stops before send other commands + delivery.Tell("stop"); + + deliverer.Tell(new Message("bar")); + + Console.WriteLine("\nSYSTEM: Throwing exception in Deliverer\n"); + deliverer.Tell("boom"); + System.Threading.Thread.Sleep(1000); + + deliverer.Tell(new Message("bar1")); + Console.WriteLine("\nSYSTEM: Enabling confirmations in 3 seconds\n"); + + System.Threading.Thread.Sleep(3000); + Console.WriteLine("\nSYSTEM: Enabled confirmations\n"); + delivery.Tell("start"); + + } + private static void ViewExample(ActorSystem system) { Console.WriteLine("\n--- PERSISTENT VIEW EXAMPLE ---\n"); diff --git a/src/examples/PersistenceExample/SnapshotedExampleActor.cs b/src/examples/PersistenceExample/SnapshotedExampleActor.cs index 113035c7d29..a66c531e46a 100644 --- a/src/examples/PersistenceExample/SnapshotedExampleActor.cs +++ b/src/examples/PersistenceExample/SnapshotedExampleActor.cs @@ -37,9 +37,9 @@ protected override bool ReceiveRecover(object message) protected override bool ReceiveCommand(object message) { - if (message == "print") + if (message as string == "print") Console.WriteLine("Current actor's state: " + State); - else if (message == "snap") + else if (message as string == "snap") SaveSnapshot(State); else if (message is SaveSnapshotFailure || message is SaveSnapshotSuccess) { } else if (message is string) diff --git a/src/examples/RemoteDeploy/Shared/Shared.csproj b/src/examples/RemoteDeploy/Shared/Shared.csproj index fe8c3c43a7f..a543fcbca0f 100644 --- a/src/examples/RemoteDeploy/Shared/Shared.csproj +++ b/src/examples/RemoteDeploy/Shared/Shared.csproj @@ -53,11 +53,7 @@ - - - - diff --git a/src/examples/RemoteDeploy/System1/System1.csproj b/src/examples/RemoteDeploy/System1/System1.csproj index 28bd0512efe..3dcf4fc5dc1 100644 --- a/src/examples/RemoteDeploy/System1/System1.csproj +++ b/src/examples/RemoteDeploy/System1/System1.csproj @@ -59,11 +59,7 @@ - - - - diff --git a/src/examples/RemoteDeploy/System2/System2.csproj b/src/examples/RemoteDeploy/System2/System2.csproj index 93d6f006685..fc7b92c7efd 100644 --- a/src/examples/RemoteDeploy/System2/System2.csproj +++ b/src/examples/RemoteDeploy/System2/System2.csproj @@ -57,11 +57,7 @@ - - - - diff --git a/src/examples/Routing/Routing.csproj b/src/examples/Routing/Routing.csproj index c53560e104f..5f9a78c0f25 100644 --- a/src/examples/Routing/Routing.csproj +++ b/src/examples/Routing/Routing.csproj @@ -57,11 +57,7 @@ - - - - diff --git a/src/examples/Stocks/SymbolLookup/SymbolLookup.csproj b/src/examples/Stocks/SymbolLookup/SymbolLookup.csproj index 0984c083189..1e6f5632e71 100644 --- a/src/examples/Stocks/SymbolLookup/SymbolLookup.csproj +++ b/src/examples/Stocks/SymbolLookup/SymbolLookup.csproj @@ -66,14 +66,10 @@ - - - - ..\..\..\packages\fastJSON.2.0.27.1\lib\net40\fastjson.dll diff --git a/src/examples/TimeServer/TimeClient/TimeClient.csproj b/src/examples/TimeServer/TimeClient/TimeClient.csproj index f3632d9b33c..ba2aee54887 100644 --- a/src/examples/TimeServer/TimeClient/TimeClient.csproj +++ b/src/examples/TimeServer/TimeClient/TimeClient.csproj @@ -36,11 +36,7 @@ - - - - ..\..\..\packages\Helios.1.4.0\lib\net45\Helios.dll diff --git a/src/examples/TimeServer/TimeServer/TimeServer.csproj b/src/examples/TimeServer/TimeServer/TimeServer.csproj index 03e5bbc9c51..8cbae4b981a 100644 --- a/src/examples/TimeServer/TimeServer/TimeServer.csproj +++ b/src/examples/TimeServer/TimeServer/TimeServer.csproj @@ -36,11 +36,7 @@ - - - - ..\..\..\packages\Helios.1.4.0\lib\net45\Helios.dll diff --git a/src/packages/repositories.config b/src/packages/repositories.config index 4dc61efdcce..180a1e8f26c 100644 --- a/src/packages/repositories.config +++ b/src/packages/repositories.config @@ -48,6 +48,8 @@ + +