diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..db670cd7 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at vaughn@kalele.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a5429500 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +## How to contribute to the vlingo/common + +#### **Did you find a bug?** + +* Make sure the bug was not already reported here: [Issues](https://github.com/vlingo-net/vlingo-net-actors/issues). + +* If nonexisting, open a new issue for the problem: [Open New Issue](https://github.com/vlingo-net/vlingo-net-actors/issues/new). Always provide a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. + +#### **Patches and bug fixes** + +* Open a new GitHub pull request with the patch. + +* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. + +* It would be really nice if your followed the basic code format used prevelently. + +#### **Please don't reformat existing code** + +* Just because you don't like a given code style doesn't mean you have the authority to change it. Cosmeic changes add zero to little value. + +#### **New features and enhancements** + +* Email your post your suggestion and provide an example implementation. + +* After agreement open a PR or issue. + +#### **Direct questions to...** + +* Vaughn Vernon: vaughn at kalele dot io + +#### **Contribute to documentation** + +* Vaughn Vernon: vaughn at kalele dot io + +Thanks for your kind assistance! :smile: + +Vaughn Vernon and the Vlingo .NET team diff --git a/src/Vlingo.Actors.Tests/ActorEnvironmentTest.cs b/src/Vlingo.Actors.Tests/ActorEnvironmentTest.cs index 7dd6a70b..c35ac4bf 100644 --- a/src/Vlingo.Actors.Tests/ActorEnvironmentTest.cs +++ b/src/Vlingo.Actors.Tests/ActorEnvironmentTest.cs @@ -5,7 +5,6 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. -using System; using Vlingo.Actors.TestKit; using Xunit; diff --git a/src/Vlingo.Actors.Tests/ActorFactoryTest.cs b/src/Vlingo.Actors.Tests/ActorFactoryTest.cs index aa504f04..00565aa8 100644 --- a/src/Vlingo.Actors.Tests/ActorFactoryTest.cs +++ b/src/Vlingo.Actors.Tests/ActorFactoryTest.cs @@ -5,6 +5,7 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. +using System; using System.Threading; using Vlingo.Actors.Plugin.Mailbox.TestKit; using Xunit; @@ -12,7 +13,7 @@ namespace Vlingo.Actors.Tests { public class ActorFactoryTest : ActorsTest - { + { [Fact] public void TestActorForWithNoParametersAndDefaults() { @@ -88,7 +89,28 @@ public void TestActorForWithParameters() Assert.Equal(mailbox, actor.LifeCycle.Environment.Mailbox); } - public sealed class ParentInterfaceActor : Actor, IParentInterface + [Fact] + public void TestConstructorFailure() + { + World.ActorFor(Definition.Has(Definition.NoParameters)); + var address = World.AddressFactory.UniqueWith("test-actor-ctor-failure"); + var definition = Definition.Has( + Definition.Parameters("test-ctor-failure", -100), + ParentInterfaceActor.Instance.Value, + address.Name); + var mailbox = new TestMailbox(); + + Assert.Throws(() => ActorFactory.ActorFor( + World.Stage, + definition.Parent, + definition, + address, + mailbox, + null, + World.DefaultLogger)); + } + + private class ParentInterfaceActor : Actor, IParentInterface { public static ThreadLocal Instance { get; } = new ThreadLocal(); @@ -100,11 +122,19 @@ public ParentInterfaceActor() public interface ITestInterface { } - internal sealed class TestInterfaceActor : Actor, ITestInterface { } + private class TestInterfaceActor : Actor, ITestInterface { } - internal sealed class TestInterfaceWithParamsActor : Actor, ITestInterface + private class TestInterfaceWithParamsActor : Actor, ITestInterface { public TestInterfaceWithParamsActor(string text, int val) { } } + + private class FailureActor : Actor, ITestInterface + { + public FailureActor(string text, int val) + { + throw new InvalidOperationException("Failed in ctor with: " + text + " and: " + val); + } + } } } \ No newline at end of file diff --git a/src/Vlingo.Actors.Tests/ActorStopTest.cs b/src/Vlingo.Actors.Tests/ActorStopTest.cs index 6e4eebb5..b7729ad6 100644 --- a/src/Vlingo.Actors.Tests/ActorStopTest.cs +++ b/src/Vlingo.Actors.Tests/ActorStopTest.cs @@ -6,8 +6,8 @@ // one at https://mozilla.org/MPL/2.0/. using System; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; using Xunit; namespace Vlingo.Actors.Tests @@ -27,13 +27,13 @@ public void TestWorldTerminateToStopAllActors() stoppables[idx].CreateChildren(); } - testSpecs.untilStart.Completes(); + testSpecs.untilStart.CompletesWithin(2000); testSpecs.untilTerminatingStop = TestUntil.Happenings(12); testSpecs.terminating.Set(true); World.Terminate(); - testSpecs.untilTerminatingStop.Completes(); + testSpecs.untilTerminatingStop.CompletesWithin(2000); Assert.Equal(12, testSpecs.terminatingStopCount.Get()); } @@ -50,7 +50,7 @@ public void TestStopActors() stoppables[idx].CreateChildren(); } - testResults.untilStart.Completes(); + testResults.untilStart.CompletesWithin(2000); testResults.untilStop = TestUntil.Happenings(12); @@ -59,13 +59,17 @@ public void TestStopActors() stoppables[idx].Stop(); } - testResults.untilStop.Completes(); + testResults.untilStop.CompletesWithin(2000); Assert.Equal(12, testResults.stopCount.Get()); + testResults.untilTerminatingStop = TestUntil.Happenings(0); + testResults.terminating.Set(true); World.Terminate(); + testResults.untilTerminatingStop.CompletesWithin(2000); + Assert.Equal(0, testResults.terminatingStopCount.Get()); } @@ -111,7 +115,8 @@ protected internal override void AfterStop() { if (testResults.terminating.Get()) { - testResults.terminatingStopCount.IncrementAndGet(); + var count = testResults.terminatingStopCount.IncrementAndGet(); + Console.WriteLine("TERMINATING AND STOPPED: " + count + " "); testResults.untilTerminatingStop.Happened(); } else diff --git a/src/Vlingo.Actors.Tests/ActorStowDisperseTest.cs b/src/Vlingo.Actors.Tests/ActorStowDisperseTest.cs new file mode 100644 index 00000000..38c9fd97 --- /dev/null +++ b/src/Vlingo.Actors.Tests/ActorStowDisperseTest.cs @@ -0,0 +1,116 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System; +using Vlingo.Actors.TestKit; +using Xunit; + +namespace Vlingo.Actors.Tests +{ + public class ActorStowDisperseTest : ActorsTest + { + [Fact] + public void TestThatStowedMessagesDisperseOnOverride() + { + var results = new Results(1, 10); + var protocols = Protocols.Two( + World.ActorFor( + Definition.Has(Definition.Parameters(results), "stow-override"), + new[] { typeof(IStowThese), typeof(IOverrideStowage) })); + + for(var idx=0; idx<10; ++idx) + { + protocols._1.Stow(); + } + + protocols._2.Override(); + + results.overrideReceived.Completes(); + results.stowReceived.Completes(); + + Assert.Equal(1, results.overrideReceivedCount); + Assert.Equal(10, results.stowReceivedCount); + } + + [Fact] + public void TestThatStowedMessagesDisperseOnCrash() + { + var results = new Results(1, 10); + var protocols = Protocols.Two( + World.ActorFor( + Definition.Has(Definition.Parameters(results), "stow-override"), + new[] { typeof(IStowThese), typeof(IOverrideStowage) })); + + for (var idx = 0; idx < 10; ++idx) + { + protocols._1.Stow(); + } + + protocols._2.Crash(); + + results.overrideReceived.Completes(); + results.stowReceived.Completes(); + + Assert.Equal(1, results.overrideReceivedCount); + Assert.Equal(10, results.stowReceivedCount); + } + + private class Results + { + public readonly TestUntil overrideReceived; + public int overrideReceivedCount; + public readonly TestUntil stowReceived; + public int stowReceivedCount; + public Results(int overrideReceived, int stowReceived) + { + this.overrideReceived = TestUntil.Happenings(overrideReceived); + this.stowReceived = TestUntil.Happenings(stowReceived); + } + } + + private class StowTestActor : Actor, IOverrideStowage, IStowThese + { + private readonly Results results; + + public StowTestActor(Results results) + { + this.results = results; + StowMessages(typeof(IOverrideStowage)); + } + + public void Crash() + { + ++results.overrideReceivedCount; + results.overrideReceived.Happened(); + throw new ApplicationException("Intended failure"); + } + + public void Override() + { + ++results.overrideReceivedCount; + DisperseStowedMessages(); + results.overrideReceived.Happened(); + } + + public void Stow() + { + ++results.stowReceivedCount; + results.stowReceived.Happened(); + } + } + } + + public interface IOverrideStowage + { + void Crash(); + void Override(); + } + public interface IStowThese + { + void Stow(); + } +} diff --git a/src/Vlingo.Actors.Tests/ActorsTest.cs b/src/Vlingo.Actors.Tests/ActorsTest.cs index 6c920856..271aa5f0 100644 --- a/src/Vlingo.Actors.Tests/ActorsTest.cs +++ b/src/Vlingo.Actors.Tests/ActorsTest.cs @@ -7,6 +7,7 @@ using System; using Vlingo.Actors.TestKit; +using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests { diff --git a/src/Vlingo.Actors.Tests/AddressTest.cs b/src/Vlingo.Actors.Tests/AddressTest.cs index 6cd258b4..dfe334f6 100644 --- a/src/Vlingo.Actors.Tests/AddressTest.cs +++ b/src/Vlingo.Actors.Tests/AddressTest.cs @@ -5,7 +5,6 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. -using System; using Xunit; namespace Vlingo.Actors.Tests @@ -32,12 +31,12 @@ public void TestNameGiven() [Fact] public void TestNameAndIdGiven() { - const int id = 123; + const long id = 123; var address = World.AddressFactory.From(id, "test-address"); Assert.NotNull(address); - Assert.Equal(123, address.Id); + Assert.Equal(id, address.Id); Assert.Equal("test-address", address.Name); var another = World.AddressFactory.From(456, "test-address"); diff --git a/src/Vlingo.Actors.Tests/BasicCompletesTest.cs b/src/Vlingo.Actors.Tests/BasicCompletesTest.cs deleted file mode 100644 index d2f573de..00000000 --- a/src/Vlingo.Actors.Tests/BasicCompletesTest.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. -// -// This Source Code Form is subject to the terms of the -// Mozilla Public License, v. 2.0. If a copy of the MPL -// was not distributed with this file, You can obtain -// one at https://mozilla.org/MPL/2.0/. - -using System; -using System.Threading; -using Xunit; - -namespace Vlingo.Actors.Tests -{ - public class BasicCompletesTest - { - [Fact] - public void TestCompletesWith() - { - var completes = new BasicCompletes(5); - - Assert.Equal(5, completes.Outcome); - } - - [Fact] - public void TestCompletesAfterSupplier() - { - var completes = new BasicCompletes(0); - completes.After(() => completes.Outcome * 2); - - completes.With(5); - - Assert.Equal(10, completes.Outcome); - } - - [Fact] - public void TestCompletesAfterConsumer() - { - int andThenValue = 0; - var completes = new BasicCompletes(0); - completes.After(x => andThenValue = x); - - completes.With(5); - - Assert.Equal(5, andThenValue); - } - - [Fact] - public void TestCompletesAfterAndThen() - { - int andThenValue = 0; - var completes = new BasicCompletes(0); - completes - .After(() => completes.Outcome * 2) - .AndThen(x => andThenValue = x); - - completes.With(5); - - Assert.Equal(10, andThenValue); - } - - [Fact] - public void TestCompletesAfterAndThenMessageOut() - { - int andThenValue = 0; - var completes = new BasicCompletes(0); - var sender = new Sender(x => andThenValue = x); - - completes - .After(() => completes.Outcome * 2) - .AndThen(x => sender.Send(x)); - - completes.With(5); - - Assert.Equal(10, andThenValue); - } - - [Fact] - public void TestOutcomeBeforeTimeout() - { - int andThenValue = 0; - var completes = new BasicCompletes(new Scheduler()); - - completes - .After(() => completes.Outcome * 2, 1000) - .AndThen(x => andThenValue = x); - - completes.With(5); - - Assert.Equal(10, andThenValue); - } - - [Fact] - public void TestTimeoutBeforeOutcome() - { - int andThenValue = 0; - var completes = new BasicCompletes(new Scheduler()); - - completes - .After(() => completes.Outcome * 2, 1, 0) - .AndThen(x => andThenValue = x); - - Thread.Sleep(1000); - completes.With(5); - - Assert.NotEqual(10, andThenValue); - Assert.Equal(0, andThenValue); - } - - private class Sender - { - private Action callback; - public Sender(Action callback) - { - if(callback != null) - { - this.callback = callback; - } - } - - internal void Send(int value) - { - callback(value); - } - } - } -} diff --git a/src/Vlingo.Actors.Tests/CharactersTest.cs b/src/Vlingo.Actors.Tests/CharactersTest.cs new file mode 100644 index 00000000..6cf33cb9 --- /dev/null +++ b/src/Vlingo.Actors.Tests/CharactersTest.cs @@ -0,0 +1,132 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; +using Vlingo.Actors.TestKit; +using Xunit; + +namespace Vlingo.Actors.Tests +{ + public class CharactersTest : IDisposable + { + private readonly World world; + + public CharactersTest() + { + world = World.StartWithDefault("become-test"); + } + + public void Dispose() => world.Terminate(); + + [Fact] + public void TestBecomeWithThreeCharacters() + { + var results = new Results(30); + var threeBehaviors = world.ActorFor( + Definition.Has(Definition.Parameters(results))); + + for (var count = 0; count < 10; ++count) + { + threeBehaviors.One(); + threeBehaviors.Two(); + threeBehaviors.Three(); + } + + results.until.Completes(); + + Assert.Equal(10, results.one); + Assert.Equal(20, results.two); + Assert.Equal(30, results.three); + } + + private class Results + { + public int one; + public int two; + public int three; + public TestUntil until; + public Results(int times) + { + until = TestUntil.Happenings(times); + } + } + + private class ThreeBehaviorsState : IThreeBehaviors + { + public const int ONE = 0, TWO = 1, THREE = 2; + + private readonly Results results; + private readonly int incrementBy; + private Characters characters; + + public ThreeBehaviorsState(Results results, int incrementBy) + { + this.results = results; + this.incrementBy = incrementBy; + } + + public void SetCharacters(Characters characters) + { + this.characters = characters; + } + + public void One() + { + results.one += incrementBy; + characters.Become(TWO); + results.until.Happened(); + } + + public void Two() + { + results.two += incrementBy; + characters.Become(THREE); + results.until.Happened(); + } + + public void Three() + { + results.three += incrementBy; + characters.Become(ONE); + results.until.Happened(); + } + } + + private class ThreeBehaviorsActor : Actor, IThreeBehaviors + { + private readonly Characters characters; + + public ThreeBehaviorsActor(Results results) + { + var one = new ThreeBehaviorsState(results, 1); + var two = new ThreeBehaviorsState(results, 2); + var three = new ThreeBehaviorsState(results, 3); + characters = new Characters(new List + { + one, two, three + }); + one.SetCharacters(characters); + two.SetCharacters(characters); + three.SetCharacters(characters); + } + + public void One() => characters.Current.One(); + + public void Two() => characters.Current.Two(); + + public void Three() => characters.Current.Three(); + } + } + + public interface IThreeBehaviors + { + void One(); + void Two(); + void Three(); + } +} diff --git a/src/Vlingo.Actors.Tests/CompletesActorProtocolTest.cs b/src/Vlingo.Actors.Tests/CompletesActorProtocolTest.cs index 8e66dfb8..566fca54 100644 --- a/src/Vlingo.Actors.Tests/CompletesActorProtocolTest.cs +++ b/src/Vlingo.Actors.Tests/CompletesActorProtocolTest.cs @@ -5,8 +5,8 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. -using System; using System.Threading; +using Vlingo.Common; using Vlingo.Actors.TestKit; using Xunit; @@ -20,8 +20,14 @@ public class CompletesActorProtocolTest : ActorsTest private string greeting; private int value; - private readonly TestUntil untilHello = TestUntil.Happenings(1); - private readonly TestUntil untilOne = TestUntil.Happenings(1); + private readonly TestUntil untilHello; + private readonly TestUntil untilOne; + + public CompletesActorProtocolTest() + { + untilHello = TestUntil.Happenings(1); + untilOne = TestUntil.Happenings(1); + } public override void Dispose() { @@ -31,27 +37,27 @@ public override void Dispose() } [Fact] - public void TestReturnsCompletes() + public void TestReturnsCompletesForSideEffects() { var uc = World.ActorFor(Definition.Has(Definition.NoParameters)); - uc.GetHello().After(hello => SetHello(hello.greeting)); + uc.GetHello().AndThenConsume(hello => SetHello(hello.greeting)); untilHello.Completes(); Assert.Equal(Hello, greeting); - uc.GetOne().After(value => SetValue(value)); + uc.GetOne().AndThenConsume(value => SetValue(value)); untilOne.Completes(); Assert.Equal(1, value); } [Fact] - public void TestAfterAndThenCompletes() + public void TestAfterAndThenCompletesForSideEffects() { var uc = World.ActorFor(Definition.Has(Definition.NoParameters)); var helloCompletes = uc.GetHello(); helloCompletes - .After(() => new Hello(Prefix + helloCompletes.Outcome.greeting)) - .AndThen(hello => SetHello(hello.greeting)); + .AndThen(hello => new Hello(Prefix + helloCompletes.Outcome.greeting)) + .AndThenConsume(hello => SetHello(hello.greeting)); untilHello.Completes(); Assert.NotEqual(Hello, helloCompletes.Outcome.greeting); @@ -61,8 +67,8 @@ public void TestAfterAndThenCompletes() var one = uc.GetOne(); one - .After(() => one.Outcome + 1) - .AndThen(value => SetValue(value)); + .AndThen(value => one.Outcome + 1) + .AndThenConsume(value => SetValue(value)); untilOne.Completes(); Assert.NotEqual(1, one.Outcome); @@ -72,17 +78,30 @@ public void TestAfterAndThenCompletes() } [Fact] - public void TestThatTimeOutOccurs() + public void TestThatTimeOutOccursForSideEffects() { var uc = World.ActorFor(Definition.Has(Definition.NoParameters)); var helloCompletes = uc.GetHello() - .After(hello => SetHello(hello.greeting), 2, new Hello(HelloNot)); + .AndThenConsume(2, new Hello(HelloNot), hello => SetHello(hello.greeting)) + .Otherwise(failedHello => + { + SetHello(failedHello.greeting); + return failedHello; + }); + untilHello.Completes(); Assert.NotEqual(Hello, greeting); Assert.Equal(HelloNot, helloCompletes.Outcome.greeting); - var oneCompletes = uc.GetOne().After(value => SetValue(value), 2, 0); + var oneCompletes = uc.GetOne() + .AndThenConsume(2, 0, value => SetValue(value)) + .Otherwise(value => + { + untilOne.Happened(); + return 0; + }); + untilOne.Completes(); Assert.NotEqual(1, value); @@ -110,6 +129,8 @@ public Hello(string greeting) { this.greeting = greeting; } + + public override string ToString() => $"Hello[{greeting}]"; } public interface IUsesCompletes @@ -120,9 +141,9 @@ public interface IUsesCompletes public class UsesCompletesActor : Actor, IUsesCompletes { - public ICompletes GetHello() => Completes().With(new Hello(CompletesActorProtocolTest.Hello)); + public ICompletes GetHello() => Completes().With(new Hello(CompletesActorProtocolTest.Hello)); - public ICompletes GetOne() => Completes().With(1); + public ICompletes GetOne() => Completes().With(1); } public class UsesCompletesCausesTimeoutActor : Actor, IUsesCompletes @@ -135,7 +156,7 @@ public ICompletes GetHello() } catch (ThreadInterruptedException) { } - return Completes().With(new Hello(CompletesActorProtocolTest.Hello)); + return Completes().With(new Hello(CompletesActorProtocolTest.Hello)); } public ICompletes GetOne() @@ -146,7 +167,7 @@ public ICompletes GetOne() } catch (ThreadInterruptedException) { } - return Completes().With(1); + return Completes().With(1); } } } \ No newline at end of file diff --git a/src/Vlingo.Actors.Tests/ContentBasedRoutingStrategyTest.cs b/src/Vlingo.Actors.Tests/ContentBasedRoutingStrategyTest.cs new file mode 100644 index 00000000..3fc3a8ad --- /dev/null +++ b/src/Vlingo.Actors.Tests/ContentBasedRoutingStrategyTest.cs @@ -0,0 +1,108 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; +using System.Linq; +using Vlingo.Actors.TestKit; +using Xunit; + +namespace Vlingo.Actors.Tests +{ + public class ContentBasedRoutingStrategyTest + { + [Fact] + public void TestThatItRoutes() + { + var world = World.StartWithDefault("ContentBasedRouterTest"); + const int poolSize = 4, messagesToSend = 40; + var until = TestUntil.Happenings(messagesToSend); + + var orderRouter = world.ActorFor(Definition.Has(Definition.Parameters(poolSize, until))); + var customerIds = new[] { "Customer1", "Customer2", "Customer3", "Customer4" }; + var random = new Random(); + + for (int i = 0; i < messagesToSend; i++) + { + var customerId = customerIds[random.Next(customerIds.Length)]; + orderRouter.RouteOrder(new Order(customerId)); + } + + until.Completes(); + } + + private class ContentBasedRoutingStrategy : RoutingStrategyAdapter + { + public override Routing ChooseRouteFor(T1 routable1, IList routees) + { + var order = routable1 as Order; + var customerId = order.CustomerId; + + return customerId == "Customer1" ? + Routing.With(routees[0]) : + Routing.With(routees[1]); + } + } + + private class OrderRouterWorker : Actor, IOrderContentRouter + { + private readonly TestUntil testUntil; + + public OrderRouterWorker(TestUntil testUntil) + { + this.testUntil = testUntil; + } + + public void RouteOrder(Order order) + { + Logger.Log($"{ToString()} is routing {order}"); + testUntil.Happened(); + } + } + + private class OrderRouterActor : Router, IOrderContentRouter + { + public OrderRouterActor(int poolSize, TestUntil testUntil) + : base( + new RouterSpecification(poolSize, Definition.Has(Definition.Parameters(testUntil)), typeof(IOrderContentRouter)), + new ContentBasedRoutingStrategy()) + { + } + + public void RouteOrder(Order order) + { + ComputeRouting(order) + .RouteesAs() + .ToList() + .ForEach(x => x.RouteOrder(order)); + } + } + } + + public interface IOrderContentRouter + { + void RouteOrder(Order order); + } + + public class Order + { + public Order(string customerId) + { + OrderId = Guid.NewGuid().ToString(); + CustomerId = customerId; + } + + public string OrderId { get; } + + public string CustomerId { get; } + + public override string ToString() + { + return $"Order[orderId={OrderId}, customerId={CustomerId}]"; + } + } +} diff --git a/src/Vlingo.Actors.Tests/DeadLettersTest.cs b/src/Vlingo.Actors.Tests/DeadLettersTest.cs index b69d57c8..3819b5ca 100644 --- a/src/Vlingo.Actors.Tests/DeadLettersTest.cs +++ b/src/Vlingo.Actors.Tests/DeadLettersTest.cs @@ -7,76 +7,87 @@ using System; using System.Collections.Generic; -using System.Threading; +using Vlingo.Actors.TestKit; using Vlingo.Actors.TestKit; using Xunit; +using static Vlingo.Actors.Tests.DeadLettersTest; namespace Vlingo.Actors.Tests { - public class DeadLettersTest : ActorsTest + public class DeadLettersTest : IDisposable { - private readonly TestActor listener; - private readonly TestActor nothing; + private readonly TestWorld world; public DeadLettersTest() - : base() { - nothing = TestWorld.ActorFor(Definition.Has(Definition.NoParameters, "nothing")); - listener = TestWorld.ActorFor(Definition.Has(Definition.NoParameters, "deadletters-listener")); - TestWorld.World.DeadLetters.RegisterListener(listener.Actor); + world = TestWorld.Start("test-dead-letters"); } + public void Dispose() => world.Terminate(); + [Fact] public void TestStoppedActorToDeadLetters() { + var result = new TestResult(3); + var nothing = world.ActorFor(Definition.Has(Definition.NoParameters, "nothing")); + var listener = world.ActorFor( + Definition.Has( + Definition.Parameters(result), "deadletters-listener")); + world.World.DeadLetters.RegisterListener(listener.Actor); + nothing.Actor.Stop(); nothing.Actor.DoNothing(1); nothing.Actor.DoNothing(2); nothing.Actor.DoNothing(3); - DeadLettersListenerActor.WaitForExpectedMessages(3); - Assert.Equal(3, DeadLettersListenerActor.deadLetters.Count); - foreach (var deadLetter in DeadLettersListenerActor.deadLetters) + result.until.Completes(); + + Assert.Equal(3, result.deadLetters.Count); + + foreach (var deadLetter in result.deadLetters) { Assert.Equal("DoNothing(int)", deadLetter.Representation); } } + + public class TestResult + { + public readonly IList deadLetters; + public readonly TestUntil until; + + public TestResult(int happenings) + { + deadLetters = new List(); + until = TestUntil.Happenings(happenings); + } + } } public interface INothing : IStoppable { - void DoNothing(int withValue); + void DoNothing(int val); } public class NothingActor : Actor, INothing { - public void DoNothing(int withValue) + public void DoNothing(int val) { } } public class DeadLettersListenerActor : Actor, IDeadLettersListener { - internal static List deadLetters = new List(); + private readonly TestResult result; - public void Handle(DeadLetter deadLetter) + public DeadLettersListenerActor(TestResult result) { - deadLetters.Add(deadLetter); + this.result = result; } - internal static void WaitForExpectedMessages(int count) + public void Handle(DeadLetter deadLetter) { - for (int idx = 0; idx < 1000; ++idx) - { - if (deadLetters.Count >= count) - { - return; - } - else - { - try { Thread.Sleep(10); } catch (Exception) { } - } - } + result.deadLetters.Add(deadLetter); + result.until.Happened(); } } } diff --git a/src/Vlingo.Actors.Tests/DirectoryTest.cs b/src/Vlingo.Actors.Tests/DirectoryTest.cs index 6a2f2d86..f57bf78f 100644 --- a/src/Vlingo.Actors.Tests/DirectoryTest.cs +++ b/src/Vlingo.Actors.Tests/DirectoryTest.cs @@ -16,7 +16,7 @@ public class DirectoryTest : ActorsTest [Fact] public void TestDirectoryRegister() { - var directory = new Directory(); + var directory = new Directory(new BasicAddress(0, "")); var address = World.AddressFactory.UniqueWith("test-actor"); var actor = new TestInterfaceActor(); @@ -29,7 +29,7 @@ public void TestDirectoryRegister() [Fact] public void TestDirectoryRemove() { - var directory = new Directory(); + var directory = new Directory(new BasicAddress(0, "")); var address = World.AddressFactory.UniqueWith("test-actor"); var actor = new TestInterfaceActor(); @@ -45,7 +45,7 @@ public void TestDirectoryRemove() [Fact] public void TestDirectoryAlreadyRegistered() { - var directory = new Directory(); + var directory = new Directory(new BasicAddress(0, "")); var address = World.AddressFactory.UniqueWith("test-actor"); var actor = new TestInterfaceActor(); @@ -57,7 +57,7 @@ public void TestDirectoryAlreadyRegistered() [Fact] public void TestDirectoryFindsRegistered() { - var directory = new Directory(); + var directory = new Directory(new BasicAddress(0, "")); var address1 = World.AddressFactory.UniqueWith("test-actor1"); var address2 = World.AddressFactory.UniqueWith("test-actor2"); var address3 = World.AddressFactory.UniqueWith("test-actor3"); diff --git a/src/Vlingo.Actors.Tests/LocalMessageTest.cs b/src/Vlingo.Actors.Tests/LocalMessageTest.cs index 33b6e57b..945bec78 100644 --- a/src/Vlingo.Actors.Tests/LocalMessageTest.cs +++ b/src/Vlingo.Actors.Tests/LocalMessageTest.cs @@ -9,6 +9,7 @@ using System.Threading; using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; using Xunit; namespace Vlingo.Actors.Tests @@ -20,7 +21,7 @@ public void TestDeliverHappy() { var testResults = new SimpleTestResults(); TestWorld.ActorFor(Definition.Has(Definition.Parameters(testResults), "test1-actor")); - Action consumer = actor => actor.Simple(); + Action consumer = x => x.Simple(); var message = new LocalMessage(SimpleActor.Instance.Value, consumer, "Simple()"); message.Deliver(); @@ -38,7 +39,7 @@ public void TestDeliverStopped() SimpleActor.Instance.Value.Stop(); - Action consumer = actor => actor.Simple(); + Action consumer = x => x.Simple(); var message = new LocalMessage(SimpleActor.Instance.Value, consumer, "Simple()"); message.Deliver(); @@ -54,7 +55,7 @@ public void TestDeliverWithParameters() TestWorld.ActorFor(Definition.Has(Definition.Parameters(testResults), "test1-actor")); testResults.UntilSimple = TestUntil.Happenings(1); - Action consumer = actor => actor.Simple2(2); + Action consumer = x => x.Simple2(2); var message = new LocalMessage(SimpleActor.Instance.Value, consumer, "Simple2(int)"); message.Deliver(); diff --git a/src/Vlingo.Actors.Tests/MockCompletes.cs b/src/Vlingo.Actors.Tests/MockCompletes.cs index b8af82aa..baebfe9d 100644 --- a/src/Vlingo.Actors.Tests/MockCompletes.cs +++ b/src/Vlingo.Actors.Tests/MockCompletes.cs @@ -5,6 +5,7 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. +using Vlingo.Common; using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests @@ -14,7 +15,7 @@ public class MockCompletes : BasicCompletes private T outcome; public MockCompletes() - : base(null) + : base((Scheduler)null) { UntilWith = TestUntil.Happenings(0); WithCount = 0; diff --git a/src/Vlingo.Actors.Tests/OutcomeInterestTest.cs b/src/Vlingo.Actors.Tests/OutcomeInterestTest.cs deleted file mode 100644 index e0099e07..00000000 --- a/src/Vlingo.Actors.Tests/OutcomeInterestTest.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. -// -// This Source Code Form is subject to the terms of the -// Mozilla Public License, v. 2.0. If a copy of the MPL -// was not distributed with this file, You can obtain -// one at https://mozilla.org/MPL/2.0/. - -using System; -using System.Threading; -using Xunit; - -namespace Vlingo.Actors.Tests -{ - public class OutcomeInterestTest : ActorsTest - { - [Fact] - public void TestOutcomeInterestSuccess() - { - var test = World.ActorFor(Definition.Has(Definition.NoParameters)); - test.DoSomething(); - - Thread.Sleep(500); - } - - private class DoSomethingOutcomeAwareActor : Actor, ITestsOutcomeAware, IOutcomeAware - { - private ITestsOutcomeInterest testsInterest; - - public DoSomethingOutcomeAwareActor() - { - testsInterest = Stage.ActorFor( - Definition.Has( - Definition.NoParameters)); - } - - public void DoSomething() - { - var interest = SelfAsOutcomeInterest(1); - testsInterest.DoSomethingWith("something", interest); - } - - public void FailureOutcome(Outcome outcome, int reference) - { - Console.WriteLine($"FAILURE: outcome={outcome} reference={reference}"); - } - - public void SuccessfulOutcome(Outcome outcome, int reference) - { - Console.WriteLine($"SUCCESS: outcome={outcome.Value} reference={reference}"); - } - } - - private class DoSomethingWithOutcomeInterestActor : Actor, ITestsOutcomeInterest - { - public void DoSomethingWith(string text, IOutcomeInterest interest) - { - interest.SuccessfulOutcome(new SuccessfulOutcome($"{text}-something-else")); - } - } - } - - public interface ITestsOutcomeAware - { - void DoSomething(); - } - - public interface ITestsOutcomeInterest - { - void DoSomethingWith(string text, IOutcomeInterest interest); - } -} diff --git a/src/Vlingo.Actors.Tests/Plugin/Completes/MockCompletesEventuallyProvider.cs b/src/Vlingo.Actors.Tests/Plugin/Completes/MockCompletesEventuallyProvider.cs index 8e9062c9..d0b9f204 100644 --- a/src/Vlingo.Actors.Tests/Plugin/Completes/MockCompletesEventuallyProvider.cs +++ b/src/Vlingo.Actors.Tests/Plugin/Completes/MockCompletesEventuallyProvider.cs @@ -5,6 +5,8 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. +using Vlingo.Common; + namespace Vlingo.Actors.Tests.Plugin.Completes { public class MockCompletesEventuallyProvider : ICompletesEventuallyProvider diff --git a/src/Vlingo.Actors.Tests/Plugin/Completes/MockRegistrar.cs b/src/Vlingo.Actors.Tests/Plugin/Completes/MockRegistrar.cs index 00f97cdf..1762aef8 100644 --- a/src/Vlingo.Actors.Tests/Plugin/Completes/MockRegistrar.cs +++ b/src/Vlingo.Actors.Tests/Plugin/Completes/MockRegistrar.cs @@ -6,6 +6,7 @@ // one at https://mozilla.org/MPL/2.0/. using System; +using Vlingo.Common; namespace Vlingo.Actors.Tests.Plugin.Completes { @@ -33,8 +34,20 @@ public void RegisterCommonSupervisor(string stageName, string name, Type supervi { } + public void RegisterCompletesEventuallyProviderKeeper(ICompletesEventuallyProviderKeeper keeper) + { + } + public void RegisterDefaultSupervisor(string stageName, string name, Type supervisorClass) { } + + public void RegisterLoggerProviderKeeper(ILoggerProviderKeeper keeper) + { + } + + public void RegisterMailboxProviderKeeper(IMailboxProviderKeeper keeper) + { + } } } diff --git a/src/Vlingo.Actors.Tests/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueDispatcherTest.cs b/src/Vlingo.Actors.Tests/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueDispatcherTest.cs index 19b60b24..0f8e8b51 100644 --- a/src/Vlingo.Actors.Tests/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueDispatcherTest.cs +++ b/src/Vlingo.Actors.Tests/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueDispatcherTest.cs @@ -7,8 +7,8 @@ using System; using Vlingo.Actors.Plugin.Mailbox.AgronaMPSCArrayQueue; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; using Xunit; namespace Vlingo.Actors.Tests.Plugin.Mailbox.AgronaMPSCArrayQueue diff --git a/src/Vlingo.Actors.Tests/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueMailboxActorTest.cs b/src/Vlingo.Actors.Tests/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueMailboxActorTest.cs index 8e2cd647..7f685835 100644 --- a/src/Vlingo.Actors.Tests/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueMailboxActorTest.cs +++ b/src/Vlingo.Actors.Tests/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueMailboxActorTest.cs @@ -8,8 +8,8 @@ using Vlingo.Actors.Plugin; using Vlingo.Actors.Plugin.Completes; using Vlingo.Actors.Plugin.Mailbox.AgronaMPSCArrayQueue; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; using Xunit; namespace Vlingo.Actors.Tests.Plugin.Mailbox.AgronaMPSCArrayQueue diff --git a/src/Vlingo.Actors.Tests/Plugin/Mailbox/ConcurrentQueue/ExecutorDispatcherTest.cs b/src/Vlingo.Actors.Tests/Plugin/Mailbox/ConcurrentQueue/ExecutorDispatcherTest.cs index 07a2bde0..e87a9e2d 100644 --- a/src/Vlingo.Actors.Tests/Plugin/Mailbox/ConcurrentQueue/ExecutorDispatcherTest.cs +++ b/src/Vlingo.Actors.Tests/Plugin/Mailbox/ConcurrentQueue/ExecutorDispatcherTest.cs @@ -8,8 +8,8 @@ using System; using System.Collections.Concurrent; using Vlingo.Actors.Plugin.Mailbox.ConcurrentQueue; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; using Xunit; namespace Vlingo.Actors.Tests.Plugin.Mailbox.ConcurrentQueue @@ -111,6 +111,10 @@ public TestMailbox(TestResults testResults) public bool IsDelivering => false; + public bool IsPreallocated => false; + + public int PendingMessages => throw new NotSupportedException("ExecutorDispatcherTest does not support this operation"); + public void Close() { } public bool Delivering(bool flag) => flag; @@ -144,6 +148,11 @@ public void Run() } public void Send(IMessage message) => queue.Enqueue(message); + + public void Send(Actor actor, Action consumer, ICompletes completes, string representation) + { + throw new NotImplementedException(); + } } private class CountTakerActor : Actor, ICountTaker diff --git a/src/Vlingo.Actors.Tests/Plugin/Mailbox/DefaultMailboxProviderKeeperPluginTest.cs b/src/Vlingo.Actors.Tests/Plugin/Mailbox/DefaultMailboxProviderKeeperPluginTest.cs new file mode 100644 index 00000000..8f847048 --- /dev/null +++ b/src/Vlingo.Actors.Tests/Plugin/Mailbox/DefaultMailboxProviderKeeperPluginTest.cs @@ -0,0 +1,72 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using NSubstitute; +using System; +using Vlingo.Actors.Plugin; +using Vlingo.Actors.Plugin.Mailbox; +using Xunit; + +namespace Vlingo.Actors.Tests.Plugin.Mailbox +{ + public class DefaultMailboxProviderKeeperPluginTest : ActorsTest + { + private readonly IPlugin plugin; + private readonly IRegistrar registrar; + private readonly IMailboxProviderKeeper keeper; + + public DefaultMailboxProviderKeeperPluginTest() + : base() + { + registrar = Substitute.For(); + keeper = Substitute.For(); + plugin = new DefaultMailboxProviderKeeperPlugin(keeper, new DefaultMailboxProviderKeeperPluginConfiguration()); + } + + [Fact] + public void TestThatItUsesTheCorrectName() + { + Assert.Equal("defaultMailboxProviderKeeper", plugin.Name); + } + + [Fact] + public void TestThatItsTheFirstPass() + { + Assert.Equal(0, plugin.Pass); + } + + [Fact] + public void TestThatStartRegistersTheProvidedKeeper() + { + plugin.Start(registrar); + + registrar.Received(1) + .RegisterMailboxProviderKeeper(Arg.Is(x => x == keeper)); + } + + [Fact] + public void TestThatReturnsTheCorrectConfiguration() + { + var configuration = plugin.Configuration; + Assert.Equal(typeof(DefaultMailboxProviderKeeperPluginConfiguration), configuration.GetType()); + } + + [Fact] + public void TestThatRegistersTheProvidedKeeperInARealWorld() + { + var properties = new Properties(); + properties.SetProperty("plugin.name.defaultMailboxProviderKeeper", "true"); + var pluginProperties = new PluginProperties("defaultMailboxProviderKeeper", properties); + plugin.Configuration.BuildWith(World.Configuration, pluginProperties); + plugin.Start(World); + World.Terminate(); + + keeper.Received(1) + .Close(); + } + } +} diff --git a/src/Vlingo.Actors.Tests/Plugin/Mailbox/DefaultMailboxProviderKeeperTest.cs b/src/Vlingo.Actors.Tests/Plugin/Mailbox/DefaultMailboxProviderKeeperTest.cs new file mode 100644 index 00000000..ea169ae7 --- /dev/null +++ b/src/Vlingo.Actors.Tests/Plugin/Mailbox/DefaultMailboxProviderKeeperTest.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using NSubstitute; +using System; +using Vlingo.Actors.Plugin.Mailbox; +using Xunit; + +namespace Vlingo.Actors.Tests.Plugin.Mailbox +{ + public class DefaultMailboxProviderKeeperTest + { + private readonly static string MAILBOX_NAME = "Mailbox-" + Guid.NewGuid().ToString(); + private readonly static int SOME_HASHCODE = Guid.NewGuid().GetHashCode(); + + private readonly IMailboxProvider mailboxProvider; + private readonly IMailboxProviderKeeper keeper; + + public DefaultMailboxProviderKeeperTest() + { + mailboxProvider = Substitute.For(); + keeper = new DefaultMailboxProviderKeeper(); + keeper.Keep(MAILBOX_NAME, false, mailboxProvider); + } + + [Fact] + public void TestThatAssignsAMailboxFromTheSpecifiedProvider() + { + keeper.AssignMailbox(MAILBOX_NAME, SOME_HASHCODE); + mailboxProvider.Received(1) + .ProvideMailboxFor(Arg.Is(x => x == SOME_HASHCODE)); + } + + [Fact] + public void TestThatKeepingADefaultMailboxOverridesTheCurrentDefault() + { + var newDefault = Substitute.For(); + var name = Guid.NewGuid().ToString(); + + keeper.Keep(name, true, newDefault); + + Assert.Equal(name, keeper.FindDefault()); + } + + [Fact] + public void TestThatClosesAllProviders() + { + keeper.Close(); + + mailboxProvider.Received(1) + .Close(); + } + + [Fact] + public void TestThatIsValidMailboxNameChecksForMailboxExistance() + { + Assert.True(keeper.IsValidMailboxName(MAILBOX_NAME)); + Assert.False(keeper.IsValidMailboxName(MAILBOX_NAME + "_does_not_exist")); + } + + [Fact] + public void TestThatAssigningAnUnknownMailboxFailsGracefully() + { + Assert.Throws(() => keeper.AssignMailbox(MAILBOX_NAME + "_does_not_exist", SOME_HASHCODE)); + } + + [Fact] + public void TestThatNoDefaultProviderWillFailGracefully() + { + Assert.Throws(() => new DefaultMailboxProviderKeeper().FindDefault()); + } + } +} diff --git a/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/PropertiesFileConfigRingBufferMailboxActorTest.cs b/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/PropertiesFileConfigRingBufferMailboxActorTest.cs new file mode 100644 index 00000000..dbf10f7c --- /dev/null +++ b/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/PropertiesFileConfigRingBufferMailboxActorTest.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using Vlingo.Actors.TestKit; +using Xunit; + +namespace Vlingo.Actors.Tests.Plugin.Mailbox.SharedRingBuffer +{ + public class PropertiesFileConfigRingBufferMailboxActorTest + { + [Fact] + public void TestThatRingBufferIsUsed() + { + var world = World.Start("ring-mailbox-test"); + var results = new TestResults(); + var one = world.ActorFor( + Definition.Has(Definition.Parameters(results), "ringMailbox", "one-behavior")); + + one.DoSomething(); + + results.until.Completes(); + + Assert.Equal(1, results.times); + } + + public class OneBehaviorActor : Actor, IOneBehavior + { + private readonly TestResults results; + + public OneBehaviorActor(TestResults results) + { + this.results = results; + } + + public void DoSomething() + { + results.times++; + results.until.Happened(); + } + } + + public class TestResults + { + public volatile int times = 0; + public TestUntil until = TestUntil.Happenings(1); + } + } + + public interface IOneBehavior + { + void DoSomething(); + } +} diff --git a/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/RingBufferDispatcherTest.cs b/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/RingBufferDispatcherTest.cs index 18416baa..a86fdebe 100644 --- a/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/RingBufferDispatcherTest.cs +++ b/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/RingBufferDispatcherTest.cs @@ -7,8 +7,8 @@ using System; using Vlingo.Actors.Plugin.Mailbox.SharedRingBuffer; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; using Xunit; namespace Vlingo.Actors.Tests.Plugin.Mailbox.SharedRingBuffer @@ -30,8 +30,7 @@ public void TestClose() { var countParam = count; Action consumer = consumerActor => consumerActor.Take(countParam); - var message = new LocalMessage(actor, consumer, "Take(int)"); - mailbox.Send(message); + mailbox.Send(actor, consumer, null, "Take(int)"); } testResults.Until.Completes(); @@ -42,10 +41,11 @@ public void TestClose() { var countParam = count; Action consumer = consumerActor => consumerActor.Take(countParam); - var message = new LocalMessage(actor, consumer, "Take(int)"); - mailbox.Send(message); + mailbox.Send(actor, consumer, null, "Take(int)"); } + Until(0).Completes(); + Assert.Equal(mailboxSize, testResults.Highest.Get()); } @@ -64,8 +64,7 @@ public void TestBasicDispatch() { var countParam = count; Action consumer = consumerActor => consumerActor.Take(countParam); - var message = new LocalMessage(actor, consumer, "Take(int)"); - mailbox.Send(message); + mailbox.Send(actor, consumer, null, "Take(int)"); } testResults.Until.Completes(); @@ -88,8 +87,7 @@ public void TestOverflowDispatch() { var countParam = count; Action consumer = consumerActor => consumerActor.Take(countParam); - var message = new LocalMessage(actor, consumer, "Take(int)"); - mailbox.Send(message); + mailbox.Send(actor, consumer, null, "Take(int)"); } dispatcher.Start(); diff --git a/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/RingBufferMailboxActorTest.cs b/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/RingBufferMailboxActorTest.cs index 7fe8c481..40966988 100644 --- a/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/RingBufferMailboxActorTest.cs +++ b/src/Vlingo.Actors.Tests/Plugin/Mailbox/SharedRingBuffer/RingBufferMailboxActorTest.cs @@ -5,11 +5,11 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. +using System; using Vlingo.Actors.Plugin; using Vlingo.Actors.Plugin.Completes; using Vlingo.Actors.Plugin.Mailbox.SharedRingBuffer; using Vlingo.Actors.TestKit; -using Vlingo.Common; using Xunit; namespace Vlingo.Actors.Tests.Plugin.Mailbox.SharedRingBuffer @@ -19,34 +19,21 @@ public class RingBufferMailboxActorTest : ActorsTest private const int MailboxSize = 64; private const int MaxCount = 1024; - public RingBufferMailboxActorTest() - { - var properties = new Properties(); - properties.SetProperty("plugin.name.testRingMailbox", "true"); - properties.SetProperty("plugin.testRingMailbox.classname", "Vlingo.Actors.Plugin.Mailbox.SharedRingBuffer.SharedRingBufferMailboxPlugin"); - properties.SetProperty("plugin.testRingMailbox.defaultMailbox", "false"); - properties.SetProperty("plugin.testRingMailbox.size", $"{MailboxSize}"); - properties.SetProperty("plugin.testRingMailbox.fixedBackoff", "2"); - properties.SetProperty("plugin.testRingMailbox.numberOfDispatchersFactor", "1.0"); - properties.SetProperty("plugin.testRingMailbox.dispatcherThrottlingCount", "10"); - - var provider = new SharedRingBufferMailboxPlugin(); - var pluginProperties = new PluginProperties("testRingMailbox", properties); - var plugin = new PooledCompletesPlugin(); - plugin.Configuration.BuildWith(World.Configuration, pluginProperties); - - provider.Start(World); - } + private const int ThroughputMailboxSize = 1048576; + private const int ThroughputMaxCount = 4194304; // 104857600; + private const int ThroughputWarmUpCount = 4194304; [Fact] public void TestBasicDispatch() { + Init(MailboxSize); var testResults = new TestResults(); var countTaker = World.ActorFor( Definition.Has( Definition.Parameters(testResults), "testRingMailbox", "countTaker-1")); var totalCount = MailboxSize / 2; testResults.Until = Until(MaxCount); + testResults.maximum = MaxCount; for (var count = 1; count <= totalCount; ++count) { @@ -54,26 +41,64 @@ public void TestBasicDispatch() } testResults.Until.Completes(); - Assert.Equal(MaxCount, testResults.Highest.Get()); + Assert.Equal(MaxCount, testResults.highest); } [Fact] - public void TestOverflowDispatch() + public void TestThroughput() { + Init(ThroughputMailboxSize); + var testResults = new TestResults(); var countTaker = World.ActorFor( - Definition.Has( + Definition.Has( Definition.Parameters(testResults), "testRingMailbox", "countTaker-2")); - var totalCount = MailboxSize * 2; - testResults.Until = Until(MaxCount); - for (var count = 1; count <= totalCount; ++count) + testResults.maximum = ThroughputWarmUpCount; + + for (var count = 1; count <= ThroughputWarmUpCount; ++count) { countTaker.Take(count); } - testResults.Until.Completes(); - Assert.Equal(MaxCount, testResults.Highest.Get()); + while (testResults.highest < ThroughputWarmUpCount) { } + + testResults.highest = 0; + testResults.maximum = ThroughputMaxCount; + + var startTime = DateTime.UtcNow; + + for (int count = 1; count <= ThroughputMaxCount; ++count) + { + countTaker.Take(count); + } + + while (testResults.highest < ThroughputMaxCount) { } + + var timeSpent = DateTime.UtcNow - startTime; + + Console.WriteLine("Ms: " + timeSpent.TotalMilliseconds + " FOR " + ThroughputMaxCount + " MESSAGES IS " + (ThroughputMaxCount / timeSpent.TotalSeconds) + " PER SECOND"); + + Assert.Equal(ThroughputMaxCount, testResults.highest); + } + + private void Init(int mailboxSize) + { + var properties = new Properties(); + properties.SetProperty("plugin.name.testRingMailbox", "true"); + properties.SetProperty("plugin.testRingMailbox.classname", "Vlingo.Actors.Plugin.Mailbox.SharedRingBuffer.SharedRingBufferMailboxPlugin"); + properties.SetProperty("plugin.testRingMailbox.defaultMailbox", "false"); + properties.SetProperty("plugin.testRingMailbox.size", $"{mailboxSize}"); + properties.SetProperty("plugin.testRingMailbox.fixedBackoff", "2"); + properties.SetProperty("plugin.testRingMailbox.numberOfDispatchersFactor", "1.0"); + properties.SetProperty("plugin.testRingMailbox.dispatcherThrottlingCount", "20"); + + var provider = new SharedRingBufferMailboxPlugin(); + var pluginProperties = new PluginProperties("testRingMailbox", properties); + var plugin = new PooledCompletesPlugin(); + plugin.Configuration.BuildWith(World.Configuration, pluginProperties); + + provider.Start(World); } private class CountTakerActor : Actor, ICountTaker @@ -89,12 +114,12 @@ public CountTakerActor(TestResults testResults) public void Take(int count) { - if (count > testResults.Highest.Get()) + if (count > testResults.highest) { - testResults.Highest.Set(count); + testResults.highest = count; testResults.Until.Happened(); } - if (count < MaxCount) + if (count < testResults.maximum) { self.Take(count + 1); } @@ -105,10 +130,26 @@ public void Take(int count) } } + private class ThroughputCountTakerActor : Actor, ICountTaker + { + private readonly TestResults testResults; + + public ThroughputCountTakerActor(TestResults testResults) + { + this.testResults = testResults; + } + + public void Take(int count) + { + testResults.highest = count; + } + } + private class TestResults { - public AtomicInteger Highest = new AtomicInteger(0); + public volatile int highest = 0; public TestUntil Until = TestUntil.Happenings(0); + public int maximum = 0; } } } diff --git a/src/Vlingo.Actors.Tests/RandomRouterTest.cs b/src/Vlingo.Actors.Tests/RandomRouterTest.cs new file mode 100644 index 00000000..96896c0a --- /dev/null +++ b/src/Vlingo.Actors.Tests/RandomRouterTest.cs @@ -0,0 +1,126 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System; +using System.Linq; +using Vlingo.Actors.TestKit; +using Xunit; + +namespace Vlingo.Actors.Tests +{ + public class RandomRouterTest + { + [Fact] + public void TestThatItRoutes() + { + var world = World.StartWithDefault("RandomRouterTest"); + const int poolSize = 4, messagesToSend = 40; + var until = TestUntil.Happenings(messagesToSend); + + var orderRouter = world.ActorFor(Definition.Has(Definition.Parameters(poolSize, until))); + var random = new Random(); + + for (int i = 0; i < messagesToSend; i++) + { + orderRouter.RouteOrder(new RandomRouterOrder()); + } + + until.Completes(); + } + + private class OrderRouterWorker : Actor, IOrderRandomRouter + { + private readonly TestUntil testUntil; + + public OrderRouterWorker(TestUntil testUntil) + { + this.testUntil = testUntil; + } + + public void RouteOrder(RandomRouterOrder order) + { + Logger.Log($"{ToString()} is routing {order}"); + testUntil.Happened(); + } + } + + private class OrderRouterActor : Router, IOrderRandomRouter + { + public OrderRouterActor(int poolSize, TestUntil testUntil) + : base( + new RouterSpecification(poolSize, Definition.Has(Definition.Parameters(testUntil)), typeof(IOrderRandomRouter)), + new RandomRoutingStrategy()) + { + } + + public void RouteOrder(RandomRouterOrder order) + { + ComputeRouting(order) + .RouteesAs() + .ToList() + .ForEach(x => x.RouteOrder(order)); + } + } + + + } + + public class RandomRouterOrder + { + public RandomRouterOrder() + { + OrderId = Guid.NewGuid().ToString(); + } + + public string OrderId { get; } + + public override string ToString() + { + return $"Order[orderId={OrderId}]"; + } + } + + public interface IOrderRandomRouter + { + void RouteOrder(RandomRouterOrder order); + } + + public class OrderRandomRouter__Proxy : IOrderRandomRouter + { + private const string RouteOrderRepresentation1 = "RouteOrder(RandomRouterOrder)"; + + private readonly Actor actor; + private readonly IMailbox mailbox; + + public OrderRandomRouter__Proxy(Actor actor, IMailbox mailbox) + { + this.actor = actor; + this.mailbox = mailbox; + } + + public void RouteOrder(RandomRouterOrder order) + { + if (!actor.IsStopped) + { + Action consumer = x => x.RouteOrder(order); + if (mailbox.IsPreallocated) + { + mailbox.Send(actor, consumer, null, RouteOrderRepresentation1); + } + else + { + mailbox.Send(new LocalMessage(actor, consumer, RouteOrderRepresentation1)); + } + } + else + { + actor.DeadLetters.FailedDelivery(new DeadLetter(actor, RouteOrderRepresentation1)); + } + } + + } +} diff --git a/src/Vlingo.Actors.Tests/RoundRobinRouterTest.cs b/src/Vlingo.Actors.Tests/RoundRobinRouterTest.cs new file mode 100644 index 00000000..945c788d --- /dev/null +++ b/src/Vlingo.Actors.Tests/RoundRobinRouterTest.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System.Linq; +using Vlingo.Actors.TestKit; +using Xunit; + +namespace Vlingo.Actors.Tests +{ + public class RoundRobinRouterTest + { + [Fact] + public void TestThatItRoutes() + { + var world = World.StartWithDefault("RoundRobinRouterTest"); + const int poolSize = 4, messagesToSend = 8; + var until = TestUntil.Happenings(messagesToSend); + var orderRouter = world.ActorFor( + Definition.Has(Definition.Parameters(poolSize, until))); + + for(var i=0; i(Definition.Parameters(testUntil)), + typeof(IRoundRobinOrderRouter)), + new RoundRobinRoutingStrategy()) + { + } + + public void RouteOrder(RoundRobinOrder order) + { + ComputeRouting(order) + .RouteesAs() + .ToList() + .ForEach(orderRoutee => orderRoutee.RouteOrder(order)); + } + } + } + + public class RoundRobinOrder + { + private readonly int orderId; + + public RoundRobinOrder(int orderId) + { + this.orderId = orderId; + } + + public int OrderId => orderId; + + public override string ToString() => "Order[orderId=" + orderId + "]"; + } + + public interface IRoundRobinOrderRouter + { + void RouteOrder(RoundRobinOrder order); + } +} diff --git a/src/Vlingo.Actors.Tests/SchedulerTest.cs b/src/Vlingo.Actors.Tests/SchedulerTest.cs index 0acba309..630bf36b 100644 --- a/src/Vlingo.Actors.Tests/SchedulerTest.cs +++ b/src/Vlingo.Actors.Tests/SchedulerTest.cs @@ -5,6 +5,7 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. +using Vlingo.Common; using Vlingo.Actors.TestKit; using Xunit; diff --git a/src/Vlingo.Actors.Tests/SmallestMailboxRouterTest.cs b/src/Vlingo.Actors.Tests/SmallestMailboxRouterTest.cs new file mode 100644 index 00000000..6ced6e28 --- /dev/null +++ b/src/Vlingo.Actors.Tests/SmallestMailboxRouterTest.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System; +using System.Linq; +using Vlingo.Actors.TestKit; +using Xunit; + +namespace Vlingo.Actors.Tests +{ + public class SmallestMailboxRouterTest + { + [Fact] + public void TestThatItRoutes() + { + var world = World.StartWithDefault("SmallestMailboxRouterTest"); + const int poolSize = 4, messagesToSend = 40; + var until = TestUntil.Happenings(messagesToSend); + + var orderRouter = world.ActorFor(Definition.Has(Definition.Parameters(poolSize, until))); + var random = new Random(); + + for (int i = 0; i < messagesToSend; i++) + { + orderRouter.RouteOrder(new SmallestRouterOrder()); + } + + until.Completes(); + } + + private class OrderRouterWorker : Actor, IOrderSmallestRouter + { + private readonly TestUntil testUntil; + + public OrderRouterWorker(TestUntil testUntil) + { + this.testUntil = testUntil; + } + + public void RouteOrder(SmallestRouterOrder order) + { + Logger.Log($"{ToString()} is routing {order}"); + testUntil.Happened(); + } + } + + private class OrderRouterActor : Router, IOrderSmallestRouter + { + public OrderRouterActor(int poolSize, TestUntil testUntil) + : base( + new RouterSpecification(poolSize, Definition.Has(Definition.Parameters(testUntil)), typeof(IOrderSmallestRouter)), + new SmallestMailboxRoutingStrategy()) + { + } + + public void RouteOrder(SmallestRouterOrder order) + { + ComputeRouting(order) + .RouteesAs() + .ToList() + .ForEach(x => x.RouteOrder(order)); + } + } + } + + public class SmallestRouterOrder + { + public SmallestRouterOrder() + { + OrderId = Guid.NewGuid().ToString(); + } + + public string OrderId { get; } + + public override string ToString() + { + return $"Order[orderId={OrderId}]"; + } + } + + public interface IOrderSmallestRouter + { + void RouteOrder(SmallestRouterOrder order); + } +} diff --git a/src/Vlingo.Actors.Tests/StageTest.cs b/src/Vlingo.Actors.Tests/StageTest.cs index 39c63d5b..8618498c 100644 --- a/src/Vlingo.Actors.Tests/StageTest.cs +++ b/src/Vlingo.Actors.Tests/StageTest.cs @@ -68,22 +68,36 @@ public void TestDirectoryScan() until.Happened(); }; - World.Stage.ActorOf(address5).After(afterConsumer); - World.Stage.ActorOf(address4).After(afterConsumer); - World.Stage.ActorOf(address3).After(afterConsumer); - World.Stage.ActorOf(address2).After(afterConsumer); - World.Stage.ActorOf(address1).After(afterConsumer); + World.Stage.ActorOf(address5).AndThenConsume(afterConsumer); + World.Stage.ActorOf(address4).AndThenConsume(afterConsumer); + World.Stage.ActorOf(address3).AndThenConsume(afterConsumer); + World.Stage.ActorOf(address2).AndThenConsume(afterConsumer); + World.Stage.ActorOf(address1).AndThenConsume(afterConsumer); - World.Stage.ActorOf(address6).After(actor => - { - Assert.Null(actor); - until.Happened(); - }); - World.Stage.ActorOf(address7).After(actor => - { - Assert.Null(actor); - until.Happened(); - }); + World.Stage.ActorOf(address6) + .AndThenConsume(actor => + { + Assert.Null(actor); + until.Happened(); + }) + .Otherwise(actor => + { + Assert.Null(actor); + until.Happened(); + return null; + }); + World.Stage.ActorOf(address7) + .AndThenConsume(actor => + { + Assert.Null(actor); + until.Happened(); + }) + .Otherwise(actor => + { + Assert.Null(actor); + until.Happened(); + return null; + }); until.Completes(); Assert.Equal(5, scanFound.Get()); diff --git a/src/Vlingo.Actors.Tests/StowageTest.cs b/src/Vlingo.Actors.Tests/StowageTest.cs index ae8d909b..dd32824e 100644 --- a/src/Vlingo.Actors.Tests/StowageTest.cs +++ b/src/Vlingo.Actors.Tests/StowageTest.cs @@ -19,14 +19,14 @@ public void TestStowHasMessages() var stowage = new Stowage(); stowage.StowingMode(); - stowage.Stow(LocalMessage()); + stowage.Stow(LocalMessage()); Assert.True(stowage.HasMessages); _ = stowage.Head; Assert.False(stowage.HasMessages); - stowage.Stow(LocalMessage()); - stowage.Stow(LocalMessage()); + stowage.Stow(LocalMessage()); + stowage.Stow(LocalMessage()); Assert.True(stowage.HasMessages); _ = stowage.Head; @@ -42,9 +42,9 @@ public void TestHead() var stowage = new Stowage(); stowage.StowingMode(); - stowage.Stow(LocalMessage()); - stowage.Stow(LocalMessage()); - stowage.Stow(LocalMessage()); + stowage.Stow(LocalMessage()); + stowage.Stow(LocalMessage()); + stowage.Stow(LocalMessage()); Assert.NotNull(stowage.Head); Assert.NotNull(stowage.Head); @@ -61,9 +61,9 @@ public void TestReset() Assert.True(stowage.IsStowing); Assert.False(stowage.IsDispersing); - stowage.Stow(LocalMessage()); - stowage.Stow(LocalMessage()); - stowage.Stow(LocalMessage()); + stowage.Stow(LocalMessage()); + stowage.Stow(LocalMessage()); + stowage.Stow(LocalMessage()); Assert.True(stowage.HasMessages); @@ -80,9 +80,9 @@ public void TestDispersing() var stowage = new Stowage(); stowage.StowingMode(); - stowage.Stow(LocalMessage("1")); - stowage.Stow(LocalMessage("2")); - stowage.Stow(LocalMessage("3")); + stowage.Stow(LocalMessage("1")); + stowage.Stow(LocalMessage("2")); + stowage.Stow(LocalMessage("3")); Assert.True(stowage.HasMessages); @@ -104,7 +104,7 @@ public void TestDispersing() Assert.False(stowage.IsDispersing); } - private static IMessage LocalMessage() => new LocalMessage(null, null, null, null); - private static IMessage LocalMessage(string encode) => new LocalMessage(null, null, null, encode); + private static IMessage LocalMessage() => new LocalMessage(null, _ => {}, null, ""); + private static IMessage LocalMessage(string encode) => new LocalMessage(null, _ => { }, null, encode); } } diff --git a/src/Vlingo.Actors.Tests/Supervision/FailureControlActor.cs b/src/Vlingo.Actors.Tests/Supervision/FailureControlActor.cs index 356d67bb..696b3abf 100644 --- a/src/Vlingo.Actors.Tests/Supervision/FailureControlActor.cs +++ b/src/Vlingo.Actors.Tests/Supervision/FailureControlActor.cs @@ -7,8 +7,8 @@ using System; using System.Threading; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests.Supervision { diff --git a/src/Vlingo.Actors.Tests/Supervision/PingActor.cs b/src/Vlingo.Actors.Tests/Supervision/PingActor.cs index db5feaae..2a900f3e 100644 --- a/src/Vlingo.Actors.Tests/Supervision/PingActor.cs +++ b/src/Vlingo.Actors.Tests/Supervision/PingActor.cs @@ -7,8 +7,8 @@ using System; using System.Threading; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests.Supervision { diff --git a/src/Vlingo.Actors.Tests/Supervision/PingSupervisorActor.cs b/src/Vlingo.Actors.Tests/Supervision/PingSupervisorActor.cs index be95f3fa..260405f8 100644 --- a/src/Vlingo.Actors.Tests/Supervision/PingSupervisorActor.cs +++ b/src/Vlingo.Actors.Tests/Supervision/PingSupervisorActor.cs @@ -7,8 +7,8 @@ using System; using System.Threading; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests.Supervision { diff --git a/src/Vlingo.Actors.Tests/Supervision/PongActor.cs b/src/Vlingo.Actors.Tests/Supervision/PongActor.cs index f6a940e5..9025b02f 100644 --- a/src/Vlingo.Actors.Tests/Supervision/PongActor.cs +++ b/src/Vlingo.Actors.Tests/Supervision/PongActor.cs @@ -7,8 +7,8 @@ using System; using System.Threading; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests.Supervision { diff --git a/src/Vlingo.Actors.Tests/Supervision/PongSupervisorActor.cs b/src/Vlingo.Actors.Tests/Supervision/PongSupervisorActor.cs index f8e8b1e8..f40caa76 100644 --- a/src/Vlingo.Actors.Tests/Supervision/PongSupervisorActor.cs +++ b/src/Vlingo.Actors.Tests/Supervision/PongSupervisorActor.cs @@ -7,8 +7,8 @@ using System; using System.Threading; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests.Supervision { diff --git a/src/Vlingo.Actors.Tests/Supervision/RestartFiveInOneSupervisorActor.cs b/src/Vlingo.Actors.Tests/Supervision/RestartFiveInOneSupervisorActor.cs index 4ea2b7a3..6a47a215 100644 --- a/src/Vlingo.Actors.Tests/Supervision/RestartFiveInOneSupervisorActor.cs +++ b/src/Vlingo.Actors.Tests/Supervision/RestartFiveInOneSupervisorActor.cs @@ -1,6 +1,6 @@ using System; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests.Supervision { diff --git a/src/Vlingo.Actors.Tests/Supervision/RestartForeverSupervisorActor.cs b/src/Vlingo.Actors.Tests/Supervision/RestartForeverSupervisorActor.cs index f299e83f..ada8292e 100644 --- a/src/Vlingo.Actors.Tests/Supervision/RestartForeverSupervisorActor.cs +++ b/src/Vlingo.Actors.Tests/Supervision/RestartForeverSupervisorActor.cs @@ -6,8 +6,8 @@ // one at https://mozilla.org/MPL/2.0/. using System; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests.Supervision { diff --git a/src/Vlingo.Actors.Tests/Supervision/ResumeForeverSupervisorActor.cs b/src/Vlingo.Actors.Tests/Supervision/ResumeForeverSupervisorActor.cs index ac492f12..a70fa3dc 100644 --- a/src/Vlingo.Actors.Tests/Supervision/ResumeForeverSupervisorActor.cs +++ b/src/Vlingo.Actors.Tests/Supervision/ResumeForeverSupervisorActor.cs @@ -6,8 +6,8 @@ // one at https://mozilla.org/MPL/2.0/. using System; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests.Supervision { diff --git a/src/Vlingo.Actors.Tests/Supervision/SuspendedSenderSupervisorActor.cs b/src/Vlingo.Actors.Tests/Supervision/SuspendedSenderSupervisorActor.cs index 48db9441..f257ea22 100644 --- a/src/Vlingo.Actors.Tests/Supervision/SuspendedSenderSupervisorActor.cs +++ b/src/Vlingo.Actors.Tests/Supervision/SuspendedSenderSupervisorActor.cs @@ -7,8 +7,8 @@ using System; using System.Threading; -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; namespace Vlingo.Actors.Tests.Supervision { diff --git a/src/Vlingo.Actors.Tests/Vlingo.Actors.Tests.csproj b/src/Vlingo.Actors.Tests/Vlingo.Actors.Tests.csproj index 00e0dad9..aa11a188 100644 --- a/src/Vlingo.Actors.Tests/Vlingo.Actors.Tests.csproj +++ b/src/Vlingo.Actors.Tests/Vlingo.Actors.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Vlingo.Actors.Tests/WorldDefaultConfigurationTest.cs b/src/Vlingo.Actors.Tests/WorldDefaultConfigurationTest.cs index c35872f6..0628f78f 100644 --- a/src/Vlingo.Actors.Tests/WorldDefaultConfigurationTest.cs +++ b/src/Vlingo.Actors.Tests/WorldDefaultConfigurationTest.cs @@ -5,8 +5,8 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. -using Vlingo.Actors.TestKit; using Vlingo.Common; +using Vlingo.Actors.TestKit; using Xunit; namespace Vlingo.Actors.Tests diff --git a/src/Vlingo.Actors.Tests/WorldTest.cs b/src/Vlingo.Actors.Tests/WorldTest.cs index dda278ea..b910e7d9 100644 --- a/src/Vlingo.Actors.Tests/WorldTest.cs +++ b/src/Vlingo.Actors.Tests/WorldTest.cs @@ -5,8 +5,10 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. -using Vlingo.Actors.TestKit; +using NSubstitute; +using System; using Vlingo.Common; +using Vlingo.Actors.TestKit; using Xunit; namespace Vlingo.Actors.Tests @@ -43,6 +45,17 @@ public void TestWorldActorFor() Assert.True(testResults.Invoked.Get()); } + [Fact] + public void TestThatARegisteredDependencyCanBeResolved() + { + var name = Guid.NewGuid().ToString(); + var dep = Substitute.For(); + World.RegisterDynamic(name, dep); + + var result = World.ResolveDynamic(name); + Assert.Same(dep, result); + } + [Fact(DisplayName = "TestTermination")] public override void Dispose() { @@ -72,6 +85,8 @@ private class TestResults public AtomicBoolean Invoked { get; set; } = new AtomicBoolean(false); public TestUntil UntilSimple { get; set; } = TestUntil.Happenings(0); } + + public interface IAnyDependecy {} } public interface ISimpleWorld diff --git a/src/Vlingo.Actors/Actor.cs b/src/Vlingo.Actors/Actor.cs index 01a1cbf1..9634aca8 100644 --- a/src/Vlingo.Actors/Actor.cs +++ b/src/Vlingo.Actors/Actor.cs @@ -6,28 +6,55 @@ // one at https://mozilla.org/MPL/2.0/. using System; +using System.Reflection; using Vlingo.Actors.TestKit; using Vlingo.Common; namespace Vlingo.Actors { + /// + /// The abstract base class of all concrete Actor types. This base provides common + /// facilities and life cycle processing for all Actor types. + /// public abstract class Actor : IStartable, IStoppable, ITestStateView { internal ResultCompletes completes; internal LifeCycle LifeCycle { get; } + /// + /// Answers the address of this Actor. + /// + /// Gets the Address of this Actor. public virtual IAddress Address => LifeCycle.Address; + /// + /// Answers the DeadLetters for this Actor. + /// + /// Gets the DeadLetters for this Actor. public virtual IDeadLetters DeadLetters => LifeCycle.Environment.Stage.World.DeadLetters; + /// + /// Answers the Scheduler for this Actor. + /// + /// Gets the Scheduler for this Actor. public virtual Scheduler Scheduler => LifeCycle.Environment.Stage.Scheduler; + /// + /// The default implementation of Start(), which is a no-op. Override if needed. + /// public virtual void Start() { } + /// + /// Answers whether or not this Actor has been stopped or is in the process or stopping. + /// + /// true if this Actor is stopped. false otherwise. public virtual bool IsStopped => LifeCycle.IsStopped; + /// + /// Initiates the process or stopping this Actor and all of its children. + /// public virtual void Stop() { if (!IsStopped) @@ -40,8 +67,17 @@ public virtual void Stop() } } + /// + /// Answers the TestState for this Actor. Override to provide a snapshot of the current Actor state. + /// + /// The TestState of this Actor. public virtual TestState ViewTestState() => new TestState(); + /// + /// Answers whether or not this Actor is equal to other. + /// + /// The object to which this Actor is compared + /// true if the two objects are of same type and has the same address. false otherwise. public override bool Equals(object other) { if (other == null || other.GetType() != GetType()) @@ -49,13 +85,25 @@ public override bool Equals(object other) return false; } - return Address.Equals(((Actor) other).LifeCycle.Address); + return Address.Equals(((Actor)other).LifeCycle.Address); } + /// + /// Answers the int hash code of this Actor. + /// + /// The hash code of this Actor. public override int GetHashCode() => LifeCycle.GetHashCode(); + /// + /// Answers the string representation of this Actor. + /// + /// The string representation of this Actor public override string ToString() => $"Actor[type={GetType().Name} address={Address}]"; + /// + /// Answers the parent Actor of this Actor. (INTERNAL ONLY) + /// + /// Gets the parent Actor. internal virtual Actor Parent { get @@ -69,19 +117,31 @@ internal virtual Actor Parent } } + /// + /// Initializes the newly created Actor. + /// protected Actor() { var maybeEnvironment = ActorFactory.ThreadLocalEnvironment.Value; LifeCycle = new LifeCycle(maybeEnvironment ?? new TestEnvironment()); ActorFactory.ThreadLocalEnvironment.Value = null; - completes = new ResultCompletes(); + completes = new ResultCompletes(); } + /// + /// Answers the protocol for the child Actor to be created by this parent Actor. + /// + /// The protocol type + /// The Definition of the child Actor to be created by this parent Actor + /// A child Actor of type created by this parent Actor. protected internal virtual T ChildActorFor(Definition definition) { if (definition.Supervisor != null) { - return LifeCycle.Environment.Stage.ActorFor(definition, this, definition.Supervisor, + return LifeCycle.Environment.Stage.ActorFor( + definition, + this, + definition.Supervisor, Logger); } else @@ -89,7 +149,7 @@ protected internal virtual T ChildActorFor(Definition definition) if (this is ISupervisor) { return LifeCycle.Environment.Stage.ActorFor( - definition, + definition, this, LifeCycle.LookUpProxy(), Logger); @@ -101,23 +161,65 @@ protected internal virtual T ChildActorFor(Definition definition) } } - protected internal virtual ICompletes Completes() + /// + /// Answers the protocol for the child Actor to be created by this parent Actor. + /// + /// The Definition of the child Actor to be created by this parent Actor + /// The type of the child Actor to be created. + /// A child Actor of type created by this parent Actor. + protected internal virtual object ChildActorFor(Definition definition, Type protocol) { - if(completes == null) + var method = this.GetType().GetMethod( + "ChildActorFor", + BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, + null, + new[] { typeof(Definition) }, + null); + + return method.MakeGenericMethod(protocol).Invoke(this, new[] { definition }); + } + + /// + /// Answers the ICompletes instance for this Actor, or null if the behavior of the currently + /// delivered IMessage does not answer a ICompletes + /// + /// ICompletes or null, depending on the current IMessage delivered. + protected internal virtual ICompletes Completes() + { + if (completes == null) { throw new InvalidOperationException("Completes is not available for this protocol behavior"); } - return (ICompletes)completes; + return completes; } + /// + /// Answers a ICompletesEventually if the behavior of the currently + /// delivered IMessage does answers a ICompletes. Otherwise the outcome + /// is undefined. + /// + /// A ICompletesEventually instance. protected internal virtual ICompletesEventually CompletesEventually() => LifeCycle.Environment.Stage.World.CompletesFor(completes.ClientCompletes()); + /// + /// Answers the Definition of this Actor. + /// + /// Gets the Definition of this Actor protected internal virtual Definition Definition => LifeCycle.Definition; + /// + /// Answers the Logger of this Actor. + /// + /// Gets the ILogger instance of this Actor protected internal virtual ILogger Logger => LifeCycle.Environment.Logger; + /// + /// Answers the parent of this Actor as the protocol. + /// + /// The protocol type for the parent Actor. + /// The parent Actor as . protected internal virtual T ParentAs() { if (LifeCycle.Environment.IsSecured) @@ -129,16 +231,28 @@ protected internal virtual T ParentAs() return LifeCycle.Environment.Stage.ActorProxyFor(parent, parent.LifeCycle.Environment.Mailbox); } + /// + /// Secures this Actor. + /// protected virtual void Secure() { LifeCycle.Secure(); } + /// + /// Answers this Actor as a protocol. This Actor must implement the protocol. + /// + /// The protocol type + /// This Actor as protected internal virtual T SelfAs() { return LifeCycle.Environment.Stage.ActorProxyFor(this, LifeCycle.Environment.Mailbox); } + /// + /// Answers the Stage of this Actor. + /// + /// Gets the Stage of this Actor protected internal Stage Stage { get @@ -152,6 +266,11 @@ protected internal Stage Stage } } + /// + /// Answers the Stage of the given name. + /// + /// The string name of the Stage to find. + /// The Stage with the given protected internal virtual Stage StageNamed(string name) { return LifeCycle.Environment.Stage.World.StageNamed(name); @@ -161,19 +280,31 @@ protected internal virtual Stage StageNamed(string name) // stowing/dispersing //======================================= + /// + /// Answers whether this Actor is currently dispersing previously stowed messages. + /// protected internal virtual bool IsDispersing => LifeCycle.IsDispersing; - + /// + /// Starts the process of dispersing any messages stowed for this Actor. + /// protected internal virtual void DisperseStowedMessages() { LifeCycle.DisperseStowedMessages(); } + /// + /// Answers whether this Actor is currently stowing messages. + /// protected internal virtual bool IsStowing => LifeCycle.IsStowing; - + /// + /// Starts the process of stowing messages for this Actor, and registers as + /// the protocol that will trigger dispersal. + /// + /// The protocol Type(s) that will trigger dispersal protected internal virtual void StowMessages(params Type[] stowageOverrides) { LifeCycle.StowMessages(); @@ -184,28 +315,49 @@ protected internal virtual void StowMessages(params Type[] stowageOverrides) // life cycle overrides //======================================= + /// + /// The message delivered before the Actor has fully started. Override to implement. + /// protected internal virtual void BeforeStart() { // override } + /// + /// The message delivered after the Actor has fully stopped. Override to implement. + /// protected internal virtual void AfterStop() { // override } + /// + /// The message delivered before the Actor has been restarted by its supervisor due to an exception. + /// Override to implement. + /// + /// The Exception cause of the supervision restart. protected internal virtual void BeforeRestart(Exception reason) { // override LifeCycle.AfterStop(this); } + /// + /// The message delivered after the Actor has been restarted by its supervisor due to an exception. + /// Override to implement. + /// + /// The Exception cause of the supervision restart. protected internal virtual void AfterRestart(Exception reason) { // override LifeCycle.BeforeStart(this); } + /// + /// The message delivered before the Actor has been resumed by its supervisor due to an exception. + /// Override to implement. + /// + /// The Exception cause of the supervision resume. protected internal virtual void BeforeResume(Exception reason) { // override diff --git a/src/Vlingo.Actors/BasicAddress.cs b/src/Vlingo.Actors/BasicAddress.cs index 22c24660..d8caeab1 100644 --- a/src/Vlingo.Actors/BasicAddress.cs +++ b/src/Vlingo.Actors/BasicAddress.cs @@ -36,6 +36,18 @@ public int CompareTo(IAddress other) public T IdTyped() => (T)(object)IdString; + public override bool Equals(object obj) + { + if (obj == null || obj.GetType() != typeof(BasicAddress)) + { + return false; + } + + return id.Equals(((BasicAddress)obj).id); + } + + public override int GetHashCode() => id.GetHashCode(); + internal BasicAddress(long reservedId) : this(reservedId, null) { } diff --git a/src/Vlingo.Actors/BroadcastRoutingStrategy.cs b/src/Vlingo.Actors/BroadcastRoutingStrategy.cs index 521876e2..e363fb5b 100644 --- a/src/Vlingo.Actors/BroadcastRoutingStrategy.cs +++ b/src/Vlingo.Actors/BroadcastRoutingStrategy.cs @@ -9,13 +9,17 @@ namespace Vlingo.Actors { + /// + /// BroadcastRoutingStrategy is a that + /// includes all pooled IList<> routees in the . + /// public class BroadcastRoutingStrategy : RoutingStrategyAdapter { public BroadcastRoutingStrategy() { } - protected override IRouting ChooseRouteFor(IEnumerable routees) + protected override Routing ChooseRouteFor(IList routees) { return Routing.With(routees); } diff --git a/src/Vlingo.Actors/Cancellable__Proxy.cs b/src/Vlingo.Actors/Cancellable__Proxy.cs index f763027d..43005051 100644 --- a/src/Vlingo.Actors/Cancellable__Proxy.cs +++ b/src/Vlingo.Actors/Cancellable__Proxy.cs @@ -23,7 +23,7 @@ public bool Cancel() { if (!actor.IsStopped) { - Action consumer = actor => actor.Cancel(); + Action consumer = x => x.Cancel(); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, "Cancel()"); diff --git a/src/Vlingo.Actors/CompletesEventually__Proxy.cs b/src/Vlingo.Actors/CompletesEventually__Proxy.cs index e375f1d5..c4c2f15c 100644 --- a/src/Vlingo.Actors/CompletesEventually__Proxy.cs +++ b/src/Vlingo.Actors/CompletesEventually__Proxy.cs @@ -29,7 +29,7 @@ public void Stop() { if (!actor.IsStopped) { - Action consumer = actor => actor.Stop(); + Action consumer = x => x.Stop(); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, RepresentationStop); @@ -49,7 +49,7 @@ public void With(object outcome) { if (!actor.IsStopped) { - Action consumer = actor => actor.With(outcome); + Action consumer = x => x.With(outcome); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, RepresentationWith); diff --git a/src/Vlingo.Actors/DeadLettersListener__Proxy.cs b/src/Vlingo.Actors/DeadLettersListener__Proxy.cs index 2361a6ad..373042c7 100644 --- a/src/Vlingo.Actors/DeadLettersListener__Proxy.cs +++ b/src/Vlingo.Actors/DeadLettersListener__Proxy.cs @@ -24,7 +24,7 @@ public void Handle(DeadLetter deadLetter) { if (!actor.IsStopped) { - Action consumer = actor => actor.Handle(deadLetter); + Action consumer = x => x.Handle(deadLetter); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, "Handle(DeadLetter)"); diff --git a/src/Vlingo.Actors/DeadLetters__Proxy.cs b/src/Vlingo.Actors/DeadLetters__Proxy.cs index d12a950f..50f59b50 100644 --- a/src/Vlingo.Actors/DeadLetters__Proxy.cs +++ b/src/Vlingo.Actors/DeadLetters__Proxy.cs @@ -26,7 +26,7 @@ public void Stop() { if (!actor.IsStopped) { - Action consumer = actor => actor.Stop(); + Action consumer = x => x.Stop(); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, "Stop()"); @@ -46,7 +46,7 @@ public void FailedDelivery(DeadLetter deadLetter) { if (!actor.IsStopped) { - Action consumer = actor => actor.FailedDelivery(deadLetter); + Action consumer = x => x.FailedDelivery(deadLetter); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, "FailedDelivery(DeadLetter)"); @@ -66,7 +66,7 @@ public void RegisterListener(IDeadLettersListener listener) { if (!actor.IsStopped) { - Action consumer = actor => actor.RegisterListener(listener); + Action consumer = x => x.RegisterListener(listener); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, "RegisterListener(DeadLettersListener)"); diff --git a/src/Vlingo.Actors/DirectoryScannerActor.cs b/src/Vlingo.Actors/DirectoryScannerActor.cs index 0acc376a..f92ed7b0 100644 --- a/src/Vlingo.Actors/DirectoryScannerActor.cs +++ b/src/Vlingo.Actors/DirectoryScannerActor.cs @@ -23,10 +23,10 @@ public ICompletes ActorOf(IAddress address) var actor = directory.ActorOf(address); if(actor != null) { - return Completes().With(Stage.ActorAs(actor)); + return Completes().With(Stage.ActorAs(actor)); } - return Completes().With(default(T)); + return Completes().With(default(T)); } } } diff --git a/src/Vlingo.Actors/DirectoryScanner__Proxy.cs b/src/Vlingo.Actors/DirectoryScanner__Proxy.cs index 822bacf0..f863e07a 100644 --- a/src/Vlingo.Actors/DirectoryScanner__Proxy.cs +++ b/src/Vlingo.Actors/DirectoryScanner__Proxy.cs @@ -27,7 +27,7 @@ public ICompletes ActorOf(IAddress address) { if (!actor.IsStopped) { - Action consumer = actor => actor.ActorOf(address); + Action consumer = x => x.ActorOf(address); var completes = new BasicCompletes(actor.Scheduler); if (mailbox.IsPreallocated) { diff --git a/src/Vlingo.Actors/ICompletesEventuallyProvider.cs b/src/Vlingo.Actors/ICompletesEventuallyProvider.cs index 585d94f8..13796b94 100644 --- a/src/Vlingo.Actors/ICompletesEventuallyProvider.cs +++ b/src/Vlingo.Actors/ICompletesEventuallyProvider.cs @@ -5,9 +5,6 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. -using System; -using System.Collections.Generic; -using System.Text; using Vlingo.Common; namespace Vlingo.Actors @@ -17,6 +14,6 @@ public interface ICompletesEventuallyProvider void Close(); ICompletesEventually CompletesEventually { get; } void InitializeUsing(Stage stage); - ICompletesEventually ProvideCompletesFor(ICompletes clientCompletes); + ICompletesEventually ProvideCompletesFor(ICompletes clientCompletes); } } diff --git a/src/Vlingo.Actors/IMessage.cs b/src/Vlingo.Actors/IMessage.cs index 09ffa758..b44dc8f1 100644 --- a/src/Vlingo.Actors/IMessage.cs +++ b/src/Vlingo.Actors/IMessage.cs @@ -16,6 +16,6 @@ public interface IMessage void Deliver(); string Representation { get; } bool IsStowed { get; } - void Set(Actor actor, Action consumer, ICompletes completes, string representation); + void Set(Actor actor, Action consumer, ICompletes completes, string representation); } } diff --git a/src/Vlingo.Actors/IRoutingStrategy.cs b/src/Vlingo.Actors/IRoutingStrategy.cs index dd7908cb..7dc2f7fa 100644 --- a/src/Vlingo.Actors/IRoutingStrategy.cs +++ b/src/Vlingo.Actors/IRoutingStrategy.cs @@ -9,6 +9,12 @@ namespace Vlingo.Actors { + /// + /// RoutingStrategy is an object that knows how to compute a + /// for a message based on a defined strategy + /// (e.g., round robin, smallest mailbox, etc.). An empty + /// is not legal and will result in an InvalidOperationException. + /// public interface IRoutingStrategy { Routing ChooseRouteFor(T1 routable1, IList routees); diff --git a/src/Vlingo.Actors/LifeCycle.cs b/src/Vlingo.Actors/LifeCycle.cs index 378580a0..292de147 100644 --- a/src/Vlingo.Actors/LifeCycle.cs +++ b/src/Vlingo.Actors/LifeCycle.cs @@ -63,7 +63,7 @@ internal void AfterStop(Actor actor) catch (Exception ex) { Environment.Logger.Log($"vlingo-dotnet/actors: Actor AfterStop() failed: {ex.Message}", ex); - Environment.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); + Environment.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); } } @@ -76,7 +76,7 @@ internal void BeforeStart(Actor actor) catch (Exception ex) { Environment.Logger.Log($"vlingo-dotnet/actors: Actor BeforeStart() failed: {ex.Message}"); - Environment.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); + Environment.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); } } @@ -89,7 +89,7 @@ internal void AfterRestart(Actor actor, Exception reason) catch (Exception ex) { Environment.Logger.Log($"vlingo-dotnet/actors: Actor AfterRestart() failed: {ex.Message}"); - Environment.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); + Environment.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); } } @@ -102,7 +102,7 @@ internal void BeforeRestart(Actor actor, Exception reason) catch (Exception ex) { Environment.Logger.Log($"vlingo-net/actors: Actor BeforeRestart() failed: {ex.Message}"); - Environment.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); + Environment.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); } } @@ -115,7 +115,7 @@ internal void BeforeResume(Actor actor, Exception reason) catch (Exception ex) { Environment.Logger.Log($"vlingo-dotnet/actors: Actor BeforeResume() failed: {ex.Message}"); - Environment.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); + Environment.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); } } @@ -123,7 +123,7 @@ internal void SendStart(Actor targetActor) { try { - Action consumer = actor => actor.Start(); + Action consumer = x => x.Start(); if (!Environment.Mailbox.IsPreallocated) { var message = new LocalMessage(targetActor, consumer, "Start()"); @@ -137,7 +137,7 @@ internal void SendStart(Actor targetActor) catch (Exception ex) { Environment.Logger.Log("vlingo-dotnet/actors: Actor Start() failed: {ex.Message}"); - Environment.Stage.HandleFailureOf(new StageSupervisedActor(targetActor, ex)); + Environment.Stage.HandleFailureOf(new StageSupervisedActor(targetActor, ex)); } } @@ -172,7 +172,7 @@ internal bool SendFirstIn(Stowage stowage) Environment.Mailbox.Send(maybeMessage); return true; } - return falase; + return false; } internal bool IsStowing => Environment.Stowage.IsStowing; diff --git a/src/Vlingo.Actors/LocalMessage.cs b/src/Vlingo.Actors/LocalMessage.cs index ba3e645a..b8724f6b 100644 --- a/src/Vlingo.Actors/LocalMessage.cs +++ b/src/Vlingo.Actors/LocalMessage.cs @@ -10,14 +10,14 @@ namespace Vlingo.Actors { - public class LocalMessage : IMessage + public class LocalMessage : IMessage { private Actor actor; - private ICompletes completes; - private Action consumer; + private ICompletes completes; + private Action consumer; private string representation; - public LocalMessage(Actor actor, Action consumer, ICompletes completes, string representation) + public LocalMessage(Actor actor, Action consumer, ICompletes completes, string representation) { this.actor = actor; this.consumer = consumer; @@ -25,12 +25,12 @@ public LocalMessage(Actor actor, Action consumer, ICompletes complete this.completes = completes; } - public LocalMessage(Actor actor, Action consumer, string representation) + public LocalMessage(Actor actor, Action consumer, string representation) : this(actor, consumer, null, representation) { } - public LocalMessage(LocalMessage message) + public LocalMessage(LocalMessage message) : this(message.actor, message.consumer, message.completes, message.representation) { } @@ -51,7 +51,7 @@ public virtual void Deliver() } else { - InternalDeliver(actor.LifeCycle.Environment.Suspended.SwapWith(this)); + InternalDeliver(actor.LifeCycle.Environment.Suspended.SwapWith(this)); } actor.LifeCycle.NextResuming(); } @@ -70,10 +70,10 @@ public virtual void Deliver() public virtual string Representation => representation; - public void Set(Actor actor, Action consumer, ICompletes completes, string representation) + public void Set(Actor actor, Action consumer, ICompletes completes, string representation) { this.actor = actor; - this.consumer = (Action)(object)consumer; + this.consumer = (TActor x) => consumer.Invoke((TConsumer)(object)x); this.representation = representation; this.completes = completes; } @@ -96,7 +96,7 @@ private void DeadLetter() private void InternalDeliver(IMessage message) { - var protocol = typeof(T); + var protocol = typeof(TActor); if (actor.IsStopped) { @@ -104,28 +104,27 @@ private void InternalDeliver(IMessage message) } else if (actor.LifeCycle.IsSuspended) { - actor.LifeCycle.Environment.Suspended.Stow(message); + actor.LifeCycle.Environment.Suspended.Stow(message); } else if (actor.IsStowing && !actor.LifeCycle.Environment.IsStowageOverride(protocol)) { - actor.LifeCycle.Environment.Stowage.Stow(message); + actor.LifeCycle.Environment.Stowage.Stow(message); } else { try { actor.completes.Reset(completes); - consumer.Invoke(actor); - if (actor.completes.__internal__outcomeSet) + consumer.Invoke((TActor)(object)actor); + if (actor.completes.HasInternalOutcomeSet) { - var outcome = actor.completes.Outcome; - actor.LifeCycle.Environment.Stage.World.CompletesFor(completes).With(actor.completes.__internal__outcome); + actor.LifeCycle.Environment.Stage.World.CompletesFor(completes).With(actor.completes.InternalOutcome); } } catch(Exception ex) { actor.Logger.Log($"Message#Deliver(): Exception: {ex.Message} for Actor: {actor} sending: {representation}", ex); - actor.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); + actor.Stage.HandleFailureOf(new StageSupervisedActor(actor, ex)); } } } diff --git a/src/Vlingo.Actors/Logger__Proxy.cs b/src/Vlingo.Actors/Logger__Proxy.cs index 89cab5f0..8356084d 100644 --- a/src/Vlingo.Actors/Logger__Proxy.cs +++ b/src/Vlingo.Actors/Logger__Proxy.cs @@ -32,7 +32,7 @@ public void Log(string message) { if (!actor.IsStopped) { - Action consumer = actor => actor.Log(message); + Action consumer = x => x.Log(message); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, LogRepresentation1); @@ -51,7 +51,7 @@ public void Log(string message, Exception ex) { if (!actor.IsStopped) { - Action consumer = actor => actor.Log(message, ex); + Action consumer = x => x.Log(message, ex); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, LogRepresentation2); @@ -70,7 +70,7 @@ public void Close() { if (!actor.IsStopped) { - Action consumer = actor => actor.Close(); + Action consumer = x => x.Close(); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, CloseRepresentation3); diff --git a/src/Vlingo.Actors/Plugin/Completes/DefaultCompletesEventuallyProviderKeeper.cs b/src/Vlingo.Actors/Plugin/Completes/DefaultCompletesEventuallyProviderKeeper.cs new file mode 100644 index 00000000..e8e36e61 --- /dev/null +++ b/src/Vlingo.Actors/Plugin/Completes/DefaultCompletesEventuallyProviderKeeper.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System; + +namespace Vlingo.Actors.Plugin.Completes +{ + public sealed class DefaultCompletesEventuallyProviderKeeper : ICompletesEventuallyProviderKeeper + { + private CompletesEventuallyProviderInfo completesEventuallyProviderInfo; + + public DefaultCompletesEventuallyProviderKeeper() + { + } + + public void Close() + { + if (completesEventuallyProviderInfo != null) + { + completesEventuallyProviderInfo.completesEventuallyProvider.Close(); + } + } + + public ICompletesEventuallyProvider FindDefault() + { + if (completesEventuallyProviderInfo == null) + { + throw new InvalidOperationException("No registered default CompletesEventuallyProvider."); + } + + return completesEventuallyProviderInfo.completesEventuallyProvider; + } + + public void Keep(string name, ICompletesEventuallyProvider completesEventuallyProvider) + { + completesEventuallyProviderInfo = new CompletesEventuallyProviderInfo(name, completesEventuallyProvider, true); + } + + public ICompletesEventuallyProvider ProviderFor(string name) + { + if (completesEventuallyProviderInfo == null) + { + throw new InvalidOperationException($"No registered CompletesEventuallyProvider named: {name}"); + } + + return completesEventuallyProviderInfo.completesEventuallyProvider; + } + + private class CompletesEventuallyProviderInfo + { + public bool isDefault; + public ICompletesEventuallyProvider completesEventuallyProvider; + public string name; + public CompletesEventuallyProviderInfo(string name, ICompletesEventuallyProvider completesEventuallyProvider, bool isDefault) + { + this.name = name; + this.completesEventuallyProvider = completesEventuallyProvider; + this.isDefault = isDefault; + } + } + } +} diff --git a/src/Vlingo.Actors/Plugin/Logging/Console/ConsoleLoggerActor.cs b/src/Vlingo.Actors/Plugin/Logging/Console/ConsoleLoggerActor.cs index 9d6cec40..1e397be2 100644 --- a/src/Vlingo.Actors/Plugin/Logging/Console/ConsoleLoggerActor.cs +++ b/src/Vlingo.Actors/Plugin/Logging/Console/ConsoleLoggerActor.cs @@ -15,6 +15,11 @@ public class ConsoleLoggerActor : Actor, ILogger public ConsoleLoggerActor(ConsoleLogger logger) { + if (logger == null) + { + throw new ArgumentNullException("ConsoleLogger can not be null"); + } + this.logger = logger; } diff --git a/src/Vlingo.Actors/Plugin/Logging/Console/ConsoleLoggerPlugin.cs b/src/Vlingo.Actors/Plugin/Logging/Console/ConsoleLoggerPlugin.cs index 3ac23eca..8223fdca 100644 --- a/src/Vlingo.Actors/Plugin/Logging/Console/ConsoleLoggerPlugin.cs +++ b/src/Vlingo.Actors/Plugin/Logging/Console/ConsoleLoggerPlugin.cs @@ -10,7 +10,7 @@ namespace Vlingo.Actors.Plugin.Logging.Console public class ConsoleLoggerPlugin : AbstractPlugin, ILoggerProvider { private readonly ConsoleLoggerPluginConfiguration consoleLoggerPluginConfiguration; - private int pass = 0; + private int pass = 1; public static ILoggerProvider RegisterStandardLogger(string name, IRegistrar registrar) { @@ -18,7 +18,7 @@ public static ILoggerProvider RegisterStandardLogger(string name, IRegistrar reg var pluginConfiguration = (ConsoleLoggerPluginConfiguration)plugin.Configuration; var properties = new Properties(); - properties.SetProperty("plugin.consoleLogger.defaulLogger", "true"); + properties.SetProperty($"plugin.{name}.defaulLogger", "true"); pluginConfiguration.BuildWith(registrar.World.Configuration, new PluginProperties(name, properties)); plugin.Start(registrar); @@ -40,14 +40,7 @@ public override void Close() Logger.Close(); } - public override int Pass - { - get - { - pass = pass == 0 ? 1 : 2; - return pass; - } - } + public override int Pass => pass; public override IPluginConfiguration Configuration => consoleLoggerPluginConfiguration; @@ -58,6 +51,7 @@ public override void Start(IRegistrar registrar) { Logger = new ConsoleLogger(consoleLoggerPluginConfiguration.Name, consoleLoggerPluginConfiguration); registrar.Register(consoleLoggerPluginConfiguration.Name, consoleLoggerPluginConfiguration.IsDefaultLogger, this); + pass = 2; } else if (pass == 2 && registrar.World != null) { diff --git a/src/Vlingo.Actors/Plugin/Logging/DefaultLoggerProviderKeeper.cs b/src/Vlingo.Actors/Plugin/Logging/DefaultLoggerProviderKeeper.cs new file mode 100644 index 00000000..f32cb6f0 --- /dev/null +++ b/src/Vlingo.Actors/Plugin/Logging/DefaultLoggerProviderKeeper.cs @@ -0,0 +1,85 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Vlingo.Actors.Plugin.Logging +{ + public sealed class DefaultLoggerProviderKeeper : ILoggerProviderKeeper + { + private readonly IDictionary loggerProviderInfos; + + public DefaultLoggerProviderKeeper() + { + loggerProviderInfos = new Dictionary(); + } + + public void Close() + { + foreach (var info in loggerProviderInfos.Values) + { + info.loggerProvider.Close(); + } + } + + public ILoggerProvider FindDefault() + => loggerProviderInfos.Values + .Where(info => info.isDefault) + .Select(info => info.loggerProvider) + .FirstOrDefault(); + + public ILoggerProvider FindNamed(string name) + { + if (loggerProviderInfos.ContainsKey(name)) + { + return loggerProviderInfos[name].loggerProvider; + } + + throw new InvalidOperationException($"No registered LoggerProvider named: {name}"); + } + + public void Keep(string name, bool isDefault, ILoggerProvider loggerProvider) + { + if (loggerProviderInfos.Count == 0 || FindDefault() == null) + { + isDefault = true; + } + + if (isDefault) + { + UndefaultCurrentDefault(); + } + + loggerProviderInfos[name] = new LoggerProviderInfo(name, loggerProvider, isDefault); + } + + private void UndefaultCurrentDefault() + { + var defaultItems = loggerProviderInfos.Where(x => x.Value.isDefault).ToList(); + defaultItems + .ForEach(item => + { + loggerProviderInfos[item.Key] = new LoggerProviderInfo(item.Value.name, item.Value.loggerProvider, false); + }); + } + + private class LoggerProviderInfo + { + public readonly bool isDefault; + public readonly ILoggerProvider loggerProvider; + public readonly string name; + public LoggerProviderInfo(string name, ILoggerProvider loggerProvider, bool isDefault) + { + this.name = name; + this.loggerProvider = loggerProvider; + this.isDefault = isDefault; + } + } + } +} diff --git a/src/Vlingo.Actors/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueMailbox.cs b/src/Vlingo.Actors/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueMailbox.cs index 82f6f185..7e966db6 100644 --- a/src/Vlingo.Actors/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueMailbox.cs +++ b/src/Vlingo.Actors/Plugin/Mailbox/AgronaMPSCArrayQueue/ManyToOneConcurrentArrayQueueMailbox.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Concurrent; +using Vlingo.Common; namespace Vlingo.Actors.Plugin.Mailbox.AgronaMPSCArrayQueue { @@ -38,6 +39,11 @@ public void Close() public bool IsDelivering => throw new NotSupportedException("ManyToOneConcurrentArrayQueueMailbox does not support this operation."); + public bool IsPreallocated => false; + + public int PendingMessages + => throw new NotSupportedException("ManyToOneConcurrentArrayQueueMailbox does not support this operation."); + public bool Delivering(bool flag) => throw new NotSupportedException("ManyToOneConcurrentArrayQueueMailbox does not support this operation."); @@ -57,5 +63,10 @@ public void Send(IMessage message) } public IMessage Receive() => queue.Take(); + + public void Send(Actor actor, Action consumer, ICompletes completes, string representation) + { + throw new NotSupportedException("Not a preallocated mailbox."); + } } } diff --git a/src/Vlingo.Actors/Plugin/Mailbox/ConcurrentQueue/ConcurrentQueueMailbox.cs b/src/Vlingo.Actors/Plugin/Mailbox/ConcurrentQueue/ConcurrentQueueMailbox.cs index b22dddef..d341abfe 100644 --- a/src/Vlingo.Actors/Plugin/Mailbox/ConcurrentQueue/ConcurrentQueueMailbox.cs +++ b/src/Vlingo.Actors/Plugin/Mailbox/ConcurrentQueue/ConcurrentQueueMailbox.cs @@ -5,6 +5,7 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. +using System; using System.Collections.Concurrent; using Vlingo.Common; @@ -27,7 +28,7 @@ internal ConcurrentQueueMailbox(IDispatcher dispatcher, int throttlingCount) public void Close() { - queue.Clear(); + // queue.Clear(); dispatcher.Close(); } @@ -54,6 +55,10 @@ public IMessage Receive() public bool IsDelivering => delivering.Get(); + public bool IsPreallocated => false; + + public int PendingMessages => queue.Count; + public bool Delivering(bool flag) => delivering.CompareAndSet(!flag, flag); public void Run() @@ -78,5 +83,10 @@ public void Run() dispatcher.Execute(this); } } + + public void Send(Actor actor, Action consumer, ICompletes completes, string representation) + { + throw new NotSupportedException("Not a preallocated mailbox."); + } } } diff --git a/src/Vlingo.Actors/Plugin/Mailbox/DefaultMailboxProviderKeeper.cs b/src/Vlingo.Actors/Plugin/Mailbox/DefaultMailboxProviderKeeper.cs new file mode 100644 index 00000000..7033c054 --- /dev/null +++ b/src/Vlingo.Actors/Plugin/Mailbox/DefaultMailboxProviderKeeper.cs @@ -0,0 +1,78 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; + +namespace Vlingo.Actors.Plugin.Mailbox +{ + public sealed class DefaultMailboxProviderKeeper : IMailboxProviderKeeper + { + private readonly IDictionary mailboxProviderInfos; + private MailboxProviderInfo defaultProvider; + + public DefaultMailboxProviderKeeper() + { + mailboxProviderInfos = new Dictionary(); + defaultProvider = null; + } + + public IMailbox AssignMailbox(string name, int hashCode) + { + if (!mailboxProviderInfos.ContainsKey(name)) + { + throw new InvalidOperationException($"No registered MailboxProvider named: {name}"); + } + + return mailboxProviderInfos[name].mailboxProvider.ProvideMailboxFor(hashCode); + } + + public void Close() + { + foreach(var info in mailboxProviderInfos.Values) + { + info.mailboxProvider.Close(); + } + } + + public string FindDefault() + { + if(defaultProvider == null) + { + throw new InvalidOperationException("No registered default MailboxProvider."); + } + + return defaultProvider.name; + } + + public bool IsValidMailboxName(string candidateMailboxName) + => mailboxProviderInfos.ContainsKey(candidateMailboxName); + + public void Keep(string name, bool isDefault, IMailboxProvider mailboxProvider) + { + var info = new MailboxProviderInfo(name, mailboxProvider); + mailboxProviderInfos[name] = info; + + if(defaultProvider == null || isDefault) + { + defaultProvider = info; + } + } + + private sealed class MailboxProviderInfo + { + public readonly IMailboxProvider mailboxProvider; + public readonly string name; + + public MailboxProviderInfo(string name, IMailboxProvider mailboxProvider) + { + this.name = name; + this.mailboxProvider = mailboxProvider; + } + } + } +} diff --git a/src/Vlingo.Actors/Plugin/Mailbox/DefaultMailboxProviderKeeperPlugin.cs b/src/Vlingo.Actors/Plugin/Mailbox/DefaultMailboxProviderKeeperPlugin.cs new file mode 100644 index 00000000..f5814c2f --- /dev/null +++ b/src/Vlingo.Actors/Plugin/Mailbox/DefaultMailboxProviderKeeperPlugin.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +namespace Vlingo.Actors.Plugin.Mailbox +{ + public class DefaultMailboxProviderKeeperPlugin : IPlugin + { + private readonly IMailboxProviderKeeper keeper; + private readonly DefaultMailboxProviderKeeperPluginConfiguration configuration; + + public DefaultMailboxProviderKeeperPlugin( + IMailboxProviderKeeper keeper, + DefaultMailboxProviderKeeperPluginConfiguration configuration) + { + this.keeper = keeper; + this.configuration = configuration; + } + + public string Name => configuration.Name; + + public int Pass => 0; + + public IPluginConfiguration Configuration => configuration; + + public void Close() + { + } + + public void Start(IRegistrar registrar) => registrar.RegisterMailboxProviderKeeper(keeper); + } +} diff --git a/src/Vlingo.Actors/Plugin/Mailbox/DefaultMailboxProviderKeeperPluginConfiguration.cs b/src/Vlingo.Actors/Plugin/Mailbox/DefaultMailboxProviderKeeperPluginConfiguration.cs new file mode 100644 index 00000000..6100c904 --- /dev/null +++ b/src/Vlingo.Actors/Plugin/Mailbox/DefaultMailboxProviderKeeperPluginConfiguration.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +namespace Vlingo.Actors.Plugin.Mailbox +{ + public class DefaultMailboxProviderKeeperPluginConfiguration : IPluginConfiguration + { + public string Name => "defaultMailboxProviderKeeper"; + + public void Build(Configuration configuration) + { + } + + public void BuildWith(Configuration configuration, PluginProperties properties) + { + } + } +} diff --git a/src/Vlingo.Actors/Plugin/Mailbox/SharedRingBuffer/RingBufferDispatcher.cs b/src/Vlingo.Actors/Plugin/Mailbox/SharedRingBuffer/RingBufferDispatcher.cs index 108419c0..f0d8f47b 100644 --- a/src/Vlingo.Actors/Plugin/Mailbox/SharedRingBuffer/RingBufferDispatcher.cs +++ b/src/Vlingo.Actors/Plugin/Mailbox/SharedRingBuffer/RingBufferDispatcher.cs @@ -28,6 +28,7 @@ public class RingBufferDispatcher : IRunnable, IDispatcher public void Close() { closed.Set(true); + Mailbox.Close(); } public void Execute(IMailbox mailbox) diff --git a/src/Vlingo.Actors/Plugin/Mailbox/SharedRingBuffer/SharedRingBufferMailbox.cs b/src/Vlingo.Actors/Plugin/Mailbox/SharedRingBuffer/SharedRingBufferMailbox.cs index 4e0e1fa5..6a85b085 100644 --- a/src/Vlingo.Actors/Plugin/Mailbox/SharedRingBuffer/SharedRingBufferMailbox.cs +++ b/src/Vlingo.Actors/Plugin/Mailbox/SharedRingBuffer/SharedRingBufferMailbox.cs @@ -8,196 +8,101 @@ using System; using System.Collections.Generic; using System.Threading; +using Vlingo.Common; namespace Vlingo.Actors.Plugin.Mailbox.SharedRingBuffer { public class SharedRingBufferMailbox : IMailbox { + private readonly AtomicBoolean closed; + private readonly IDispatcher dispatcher; private readonly int mailboxSize; - private readonly OverflowQueue overflowQueue; private readonly IMessage[] messages; - private int sendIndex; - private int receiveIndex; + private readonly AtomicLong sendIndex; + private readonly AtomicLong readyIndex; + private readonly AtomicLong receiveIndex; - internal SharedRingBufferMailbox(IDispatcher dispatcher, int mailboxSize) + protected internal SharedRingBufferMailbox(IDispatcher dispatcher, int mailboxSize) { this.dispatcher = dispatcher; this.mailboxSize = mailboxSize; - overflowQueue = new OverflowQueue(this); + closed = new AtomicBoolean(false); messages = new IMessage[mailboxSize]; - receiveIndex = 0; - sendIndex = 0; + readyIndex = new AtomicLong(-1); + receiveIndex = new AtomicLong(-1); + sendIndex = new AtomicLong(-1); + + InitPreallocated(); } - public void Close() + public virtual void Close() { - if (!IsClosed) + if (!closed.Get()) { - IsClosed = true; + closed.Set(true); dispatcher.Close(); - overflowQueue.Close(); - Clear(); } } - public bool IsClosed { get; private set; } + public virtual bool IsClosed => closed.Get(); + + public virtual bool IsDelivering + => throw new NotSupportedException("SharedRingBufferMailbox does not support this operation."); + + public virtual bool Delivering(bool flag) + => throw new NotSupportedException("SharedRingBufferMailbox does not support this operation."); - public bool IsDelivering => throw new NotSupportedException("SharedRingBufferMailbox does not support this operation."); + public virtual bool IsPreallocated => true; - public bool Delivering(bool flag) => throw new NotSupportedException("SharedRingBufferMailbox does not support this operation."); + public int PendingMessages => throw new NotSupportedException("SharedRingBufferMailbox does not support this operation"); - public int OverflowCount => overflowQueue.Count; + public virtual void Send(IMessage message) => throw new NotSupportedException("Use preallocated mailbox Send(Actor, ...)."); - public void Send(IMessage message) + public virtual void Send(Actor actor, Action consumer, ICompletes completes, string representation) { - lock (messages) + var messageIndex = sendIndex.IncrementAndGet(); + var ringSendIndex = (int)(messageIndex % mailboxSize); + int retries = 0; + while (ringSendIndex == (int)(receiveIndex.Get() % mailboxSize)) { - if(messages[sendIndex] == null) + if (++retries >= mailboxSize) { - messages[sendIndex] = message; - if(++sendIndex >= mailboxSize) + if (closed.Get()) { - sendIndex = 0; + return; } - - if (dispatcher.RequiresExecutionNotification) + else { - dispatcher.Execute(this); + retries = 0; } } - else - { - overflowQueue.DelayedSend(message); - dispatcher.Execute(this); - } } - } - public IMessage Receive() - { - var message = messages[receiveIndex]; - if (message != null) - { - messages[receiveIndex] = null; - if (++receiveIndex >= mailboxSize) - { - receiveIndex = 0; - } - if (overflowQueue.IsOverflowed) - { - overflowQueue.Execute(); - } - } - return message; + messages[ringSendIndex].Set(actor, consumer, completes, representation); + while (readyIndex.CompareAndSet(messageIndex - 1, messageIndex)) + { } } - public void Run() => throw new NotSupportedException("SharedRingBufferMailbox does not support this operation."); - - private bool CanSend() + public virtual IMessage Receive() { - var index = sendIndex; - if (index >= mailboxSize) + var messageIndex = receiveIndex.Get(); + if (messageIndex < readyIndex.Get()) { - index = 0; + var index = (int)(receiveIndex.IncrementAndGet() % mailboxSize); + return messages[index]; } - return messages[index] == null; + return null; } - private void Clear() - { - for (int idx = 0; idx < mailboxSize; ++idx) - { - messages[idx] = null; - } - } + public virtual void Run() => throw new NotSupportedException("SharedRingBufferMailbox does not support this operation."); - private class OverflowQueue : IRunnable + private void InitPreallocated() { - private Backoff backoff; - private Queue messages; - private bool open; - - internal OverflowQueue(SharedRingBufferMailbox parent) - { - backoff = new Backoff(); - messages = new Queue(); - open = false; - this.parent = parent; - } - - public void Run() - { - while (open) - { - if (parent.CanSend()) - { - - if (messages.TryDequeue(out IMessage delayed)) - { - backoff.Reset(); - parent.Send(delayed); - } - else - { - backoff.Now(); - } - } - else - { - backoff.Now(); - } - } - } - - private Thread _internalThread; - private readonly object _threadMutex = new object(); - private readonly SharedRingBufferMailbox parent; - - private void Start() - { - lock (_threadMutex) - { - if(_internalThread != null) - { - return; - } - _internalThread = new Thread(Run); - _internalThread.Start(); - } - } - - internal int Count => messages.Count; - - public bool IsOverflowed => open && messages.Count > 0; - - internal void Close() - { - open = false; - messages.Clear(); - } - - internal void DelayedSend(IMessage message) - { - messages.Enqueue(message); - if (!open) - { - open = true; - Start(); - } - else - { - Execute(); - } - } - - internal void Execute() + for (int idx = 0; idx < mailboxSize; ++idx) { - if (_internalThread != null) - { - _internalThread.Interrupt(); - } + messages[idx] = new LocalMessage(this); } } } diff --git a/src/Vlingo.Actors/Plugin/Mailbox/TestKit/TestMailbox.cs b/src/Vlingo.Actors/Plugin/Mailbox/TestKit/TestMailbox.cs index 80078f6e..a8942013 100644 --- a/src/Vlingo.Actors/Plugin/Mailbox/TestKit/TestMailbox.cs +++ b/src/Vlingo.Actors/Plugin/Mailbox/TestKit/TestMailbox.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using Vlingo.Actors.TestKit; +using Vlingo.Common; namespace Vlingo.Actors.Plugin.Mailbox.TestKit { @@ -16,11 +17,11 @@ public class TestMailbox : IMailbox public const string Name = "testerMailbox"; private readonly IList lifecycleMessages = new List { "Start", "AfterStop", "BeforeRestart", "AfterRestart" }; - private readonly TestWorld testWorld; + private readonly TestWorld world; public TestMailbox() { - testWorld = TestWorld.Instance; + world = TestWorld.Instance; } public void Run() @@ -35,6 +36,10 @@ public void Close() public bool IsDelivering => throw new NotSupportedException("TestMailbox does not support this operation."); + public bool IsPreallocated => false; + + public int PendingMessages => throw new NotSupportedException("TestMailbox does not support this operation."); + public bool Delivering(bool flag) => throw new NotSupportedException("TestMailbox does not support this operation."); public void Send(IMessage message) @@ -43,7 +48,7 @@ public void Send(IMessage message) { if (!IsLifecycleMessage(message)) { - testWorld.Track(message); + world.Track(message); } } @@ -58,5 +63,10 @@ private bool IsLifecycleMessage(IMessage message) var openParenIndex = representation.IndexOf('('); return lifecycleMessages.Contains(representation.Substring(0, openParenIndex)); } + + public void Send(Actor actor, Action consumer, ICompletes completes, string representation) + { + throw new NotSupportedException("Not a preallocated mailbox."); + } } } diff --git a/src/Vlingo.Actors/Plugin/Supervision/ConfiguredSupervisor.cs b/src/Vlingo.Actors/Plugin/Supervision/ConfiguredSupervisor.cs index b506e65e..a297ab59 100644 --- a/src/Vlingo.Actors/Plugin/Supervision/ConfiguredSupervisor.cs +++ b/src/Vlingo.Actors/Plugin/Supervision/ConfiguredSupervisor.cs @@ -6,7 +6,7 @@ namespace Vlingo.Actors.Plugin.Supervision internal class ConfiguredSupervisor { private static readonly Lazy ClassLoaderSingleton = new Lazy( - () => new DynaClassLoader(typeof(ConfiguredSupervisor).GetAssemblyLoadContext()), true); + () => new DynaClassLoader(), true); private static DynaClassLoader ClassLoader => ClassLoaderSingleton.Value; diff --git a/src/Vlingo.Actors/PooledCompletes.cs b/src/Vlingo.Actors/PooledCompletes.cs index 1a74d0dc..97c0e057 100644 --- a/src/Vlingo.Actors/PooledCompletes.cs +++ b/src/Vlingo.Actors/PooledCompletes.cs @@ -13,13 +13,13 @@ public class PooledCompletes : ICompletesEventually { public long Id { get; } - public ICompletes ClientCompletes { get; } + public ICompletes ClientCompletes { get; } public ICompletesEventually CompletesEventually { get; } public PooledCompletes( long id, - ICompletes clientCompletes, + ICompletes clientCompletes, ICompletesEventually completesEventually) { Id = id; diff --git a/src/Vlingo.Actors/Properties.cs b/src/Vlingo.Actors/Properties.cs index bd0eddb7..adc3cca4 100644 --- a/src/Vlingo.Actors/Properties.cs +++ b/src/Vlingo.Actors/Properties.cs @@ -62,9 +62,9 @@ public void Load(FileInfo configFile) continue; } - var items = line.Split('=', StringSplitOptions.RemoveEmptyEntries); + var items = line.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries); var key = items[0].Trim(); - var val = string.Join('=', items.Skip(1)).Trim(); + var val = string.Join("=", items.Skip(1)).Trim(); SetProperty(key, val); } } diff --git a/src/Vlingo.Actors/ProxyGenerator.cs b/src/Vlingo.Actors/ProxyGenerator.cs index 4314e8a2..aa3f953e 100644 --- a/src/Vlingo.Actors/ProxyGenerator.cs +++ b/src/Vlingo.Actors/ProxyGenerator.cs @@ -123,7 +123,7 @@ private string ImportStatements( namespaces.Add("System"); namespaces.Add("System.Collections.Generic"); namespaces.Add(typeof(Actor).Namespace); - namespaces.Add(typeof(ResultCompletes).Namespace); // Vlingo.Common + namespaces.Add(typeof(AtomicBoolean).Namespace); // Vlingo.Common namespaces.Add(protocolInterface.Namespace); @@ -131,7 +131,7 @@ private string ImportStatements( .ToList() .ForEach(t => namespaces.Add(t.Namespace)); - return string.Join('\n', namespaces.Select(x => $"using {x};")); + return string.Join("\n", namespaces.Select(x => $"using {x};")); } @@ -250,7 +250,7 @@ private string GetMethodDefinition(Type protocolInterface, MethodInfo method, in methodParamSignature); var ifNotStopped = " if(!actor.IsStopped)\n {"; - var consumerStatement = string.Format(" Action<{0}> consumer = actor => actor.{1}({2});", + var consumerStatement = string.Format(" Action<{0}> consumer = x => x.{1}({2});", GetSimpleTypeName(protocolInterface), method.Name, string.Join(", ", method.GetParameters().Select(p => p.Name))); @@ -258,18 +258,18 @@ private string GetMethodDefinition(Type protocolInterface, MethodInfo method, in var representationName = string.Format("{0}Representation{1}", method.Name, count); var mailboxSendStatement = string.Format( " if(mailbox.IsPreallocated)\n" + - " {\n" + + " {{\n" + " mailbox.Send(actor, consumer, {0}, {1});\n" + - " }\n" + + " }}\n" + " else\n" + - " {\n" + + " {{\n" + " mailbox.Send(new LocalMessage<{2}>(actor, consumer, {3}{1}));\n" + - " }", + " }}", isACompletes ? "completes" : "null", representationName, GetSimpleTypeName(protocolInterface), isACompletes ? "completes, " : ""); - var completesReturnStatement = isACompletes ? " return completes;\n" : ""; + var completesReturnStatement = isACompletes ? " return completes;\n" : ""; var elseDead = string.Format(" actor.DeadLetters.FailedDelivery(new DeadLetter(actor, {0}));", representationName); var returnValue = DefaultReturnValueString(method.ReturnType); var returnStatement = string.IsNullOrEmpty(returnValue) ? "" : string.Format(" return {0};\n", returnValue); diff --git a/src/Vlingo.Actors/PubSub/AffectedSubscriptions.cs b/src/Vlingo.Actors/PubSub/AffectedSubscriptions.cs new file mode 100644 index 00000000..ec7cab5e --- /dev/null +++ b/src/Vlingo.Actors/PubSub/AffectedSubscriptions.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; + +namespace Vlingo.Actors.PubSub +{ + public class AffectedSubscriptions + { + private readonly IDictionary registry; + + public AffectedSubscriptions() + { + registry = new Dictionary(); + } + + public virtual void Add(Topic topic, ISubscriber subscriber) => registry[topic] = subscriber; + + public virtual bool HasAny => registry.Count > 0; + } +} diff --git a/src/Vlingo.Actors/PubSub/Condition.cs b/src/Vlingo.Actors/PubSub/Condition.cs new file mode 100644 index 00000000..b679922e --- /dev/null +++ b/src/Vlingo.Actors/PubSub/Condition.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System; +using System.Collections.Generic; + +namespace Vlingo.Actors.PubSub +{ + internal delegate bool Condition(KeyValuePair> subscription, Topic topic, ISubscriber subscriber); + + internal static class ConditionExtension + { + public static bool Should( + this Condition condition, + KeyValuePair> subscription, + Topic topic, + ISubscriber subscriber) + => condition.Invoke(subscription, topic, subscriber); + } +} diff --git a/src/Vlingo.Actors/PubSub/DefaultPublisher.cs b/src/Vlingo.Actors/PubSub/DefaultPublisher.cs new file mode 100644 index 00000000..547c47b6 --- /dev/null +++ b/src/Vlingo.Actors/PubSub/DefaultPublisher.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +namespace Vlingo.Actors.PubSub +{ + public class DefaultPublisher : IPublisher + { + private readonly Subscriptions subscriptions; + + public DefaultPublisher() + { + subscriptions = new Subscriptions(); + } + + public virtual void Publish(Topic topic, IMessage message) + { + foreach(var subscriber in subscriptions.ForTopic(topic)) + { + subscriber.Receive(message); + } + } + + public virtual bool Subscribe(Topic topic, ISubscriber subscriber) + { + var affectedSubscriptions = subscriptions.Create(topic, subscriber); + return affectedSubscriptions.HasAny; + } + + public virtual bool Unsubscribe(Topic topic, ISubscriber subscriber) + { + var affectedSubscriptions = subscriptions.Cancel(topic, subscriber); + return affectedSubscriptions.HasAny; + } + + public virtual void UnsubscribeAllTopics(ISubscriber subscriber) + => subscriptions.CancelAll(subscriber); + } +} diff --git a/src/Vlingo.Actors/PubSub/IMessage.cs b/src/Vlingo.Actors/PubSub/IMessage.cs new file mode 100644 index 00000000..3a09b097 --- /dev/null +++ b/src/Vlingo.Actors/PubSub/IMessage.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +namespace Vlingo.Actors.PubSub +{ + public interface IMessage + { + } +} diff --git a/src/Vlingo.Actors/PubSub/IPublisher.cs b/src/Vlingo.Actors/PubSub/IPublisher.cs new file mode 100644 index 00000000..dae1c928 --- /dev/null +++ b/src/Vlingo.Actors/PubSub/IPublisher.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +namespace Vlingo.Actors.PubSub +{ + public interface IPublisher + { + void Publish(Topic topic, IMessage message); + bool Subscribe(Topic topic, ISubscriber subscriber); + bool Unsubscribe(Topic topic, ISubscriber subscriber); + void UnsubscribeAllTopics(ISubscriber subscriber); + } +} diff --git a/src/Vlingo.Actors/PubSub/ISubscriber.cs b/src/Vlingo.Actors/PubSub/ISubscriber.cs new file mode 100644 index 00000000..a0ecc695 --- /dev/null +++ b/src/Vlingo.Actors/PubSub/ISubscriber.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +namespace Vlingo.Actors.PubSub +{ + public interface ISubscriber + { + void Receive(IMessage message); + } +} diff --git a/src/Vlingo.Actors/PubSub/Operation.cs b/src/Vlingo.Actors/PubSub/Operation.cs new file mode 100644 index 00000000..cbeecc1a --- /dev/null +++ b/src/Vlingo.Actors/PubSub/Operation.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; + +namespace Vlingo.Actors.PubSub +{ + internal delegate bool Operation(ISet existingSubscriber, ISubscriber givenSubscriber); + + internal static class OperationExtension + { + public static bool Perform( + this Operation operation, + ISet existingSubscriber, + ISubscriber givenSubscriber) + => operation.Invoke(existingSubscriber, givenSubscriber); + } +} diff --git a/src/Vlingo.Actors/PubSub/Subscriptions.cs b/src/Vlingo.Actors/PubSub/Subscriptions.cs new file mode 100644 index 00000000..35ab1f89 --- /dev/null +++ b/src/Vlingo.Actors/PubSub/Subscriptions.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; +using System.Linq; + +namespace Vlingo.Actors.PubSub +{ + public class Subscriptions + { + private readonly IDictionary> index; + + public virtual AffectedSubscriptions Create(Topic topic, ISubscriber subscriber) + { + if (!index.ContainsKey(topic)) + { + index[topic] = new HashSet(); + } + return PerformOperation(topic, subscriber, DefaultCondition(), InsertOperation()); + } + + public virtual AffectedSubscriptions Cancel(Topic topic, ISubscriber subscriber) + => PerformOperation(topic, subscriber, DefaultCondition(), RemovalOperation()); + + public virtual AffectedSubscriptions CancelAll(ISubscriber subscriber) + => PerformOperation(null, subscriber, NoCondition(), RemovalOperation()); + + public virtual ISet ForTopic(Topic topic) + { + var subscribers = new HashSet(); + foreach(var subscription in index) + { + var subscribedTopic = subscription.Key; + if (subscribedTopic.Equals(topic) || subscribedTopic.IsSubTopic(topic)) + { + subscription.Value.ToList().ForEach(x => subscribers.Add(x)); + } + }; + + return subscribers; + } + + private Operation InsertOperation() + => (existingValues, givenValue) => existingValues.Add(givenValue); + + private Operation RemovalOperation() + => (existingValues, givenValue) => existingValues.Remove(givenValue); + + private Condition DefaultCondition() + => (subscription, topic, subscriber) => subscription.Key.Equals(topic); + + private Condition NoCondition() + => (subscription, topic, subscriber) => true; + + private AffectedSubscriptions PerformOperation( + Topic topic, + ISubscriber subscriber, + Condition condition, + Operation operation) + { + var affectedSubscriptions = new AffectedSubscriptions(); + foreach (var subscription in index) + { + if (condition.Should(subscription, topic, subscriber) && operation.Perform(subscription.Value, subscriber)) + { + affectedSubscriptions.Add(topic, subscriber); + } + } + + return affectedSubscriptions; + } + } +} diff --git a/src/Vlingo.Actors/PubSub/Topic.cs b/src/Vlingo.Actors/PubSub/Topic.cs new file mode 100644 index 00000000..6f36ba96 --- /dev/null +++ b/src/Vlingo.Actors/PubSub/Topic.cs @@ -0,0 +1,36 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +namespace Vlingo.Actors.PubSub +{ + public abstract class Topic + { + public Topic(string name) + { + Name = name; + } + + public virtual string Name { get; } + + public abstract bool IsSubTopic(Topic anotherTopic); + + public override bool Equals(object obj) + { + if(obj == null || obj.GetType() != GetType()) + { + return false; + } + + var otherTopic = (Topic)obj; + + return string.Equals(Name, otherTopic.Name); + } + + public override int GetHashCode() + => $"Topic[{GetType().FullName}]-{Name}".GetHashCode(); + } +} diff --git a/src/Vlingo.Actors/RandomRoutingStrategy.cs b/src/Vlingo.Actors/RandomRoutingStrategy.cs index db059fbc..23ead1bf 100644 --- a/src/Vlingo.Actors/RandomRoutingStrategy.cs +++ b/src/Vlingo.Actors/RandomRoutingStrategy.cs @@ -10,6 +10,11 @@ namespace Vlingo.Actors { + /// + /// RandomRoutingStrategy is a that + /// includes a random one of the pooled IList<> routees + /// in the + /// public class RandomRoutingStrategy : RoutingStrategyAdapter { private readonly Random random; diff --git a/src/Vlingo.Actors/ResultCompletes.cs b/src/Vlingo.Actors/ResultCompletes.cs new file mode 100644 index 00000000..649a19e6 --- /dev/null +++ b/src/Vlingo.Actors/ResultCompletes.cs @@ -0,0 +1,189 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System; + +namespace Vlingo.Common +{ + internal abstract class ResultCompletes : ICompletes + { + private ICompletes _internalClientCompletes; + internal ICompletes InternalClientCompletes + { + get => resultHolder._internalClientCompletes; + set => resultHolder._internalClientCompletes = value; + } + + private object _internalOutcome; + internal object InternalOutcome + { + get => resultHolder._internalOutcome; + set => resultHolder._internalOutcome = value; + } + + private bool _hasInternalOutcome; + internal bool HasInternalOutcomeSet + { + get => resultHolder._hasInternalOutcome; + set => resultHolder._hasInternalOutcome = value; + } + + protected ResultCompletes resultHolder; + + public abstract ICompletes With(O outcome); + public abstract ICompletes ClientCompletes(); + public abstract void Reset(ICompletes clientCompletes); + public abstract bool IsOfSameGenericType(); + } + + internal class ResultCompletes : ResultCompletes, ICompletes + { + public ResultCompletes() + : this(null, null, false) + { + } + + private ResultCompletes(ICompletes clientCompletes, object internalOutcome, bool hasOutcomeSet) + { + resultHolder = this; + InternalClientCompletes = clientCompletes; + InternalOutcome = internalOutcome; + HasInternalOutcomeSet = hasOutcomeSet; + } + + public virtual bool IsCompleted => throw new NotSupportedException(); + + public virtual bool HasFailed => throw new NotSupportedException(); + + public virtual bool HasOutcome => throw new NotSupportedException(); + + public virtual T Outcome => throw new NotSupportedException(); + + public virtual ICompletes AndThen(long timeout, T failedOutcomeValue, Func function) + { + throw new NotSupportedException(); + } + + public virtual ICompletes AndThen(T failedOutcomeValue, Func function) + { + throw new NotSupportedException(); + } + + public virtual ICompletes AndThen(long timeout, Func function) + { + throw new NotSupportedException(); + } + + public virtual ICompletes AndThen(Func function) + { + throw new NotSupportedException(); + } + + public virtual ICompletes AndThenConsume(long timeout, T failedOutcomeValue, Action consumer) + { + throw new NotSupportedException(); + } + + public virtual ICompletes AndThenConsume(T failedOutcomeValue, Action consumer) + { + throw new NotSupportedException(); + } + + public virtual ICompletes AndThenConsume(long timeout, Action consumer) + { + throw new NotSupportedException(); + } + + public virtual ICompletes AndThenConsume(Action consumer) + { + throw new NotSupportedException(); + } + + public virtual O AndThenInto(long timeout, F failedOutcomeValue, Func function) + { + throw new NotSupportedException(); + } + + public virtual O AndThenInto(F failedOutcomeValue, Func function) + { + throw new NotSupportedException(); + } + + public virtual O AndThenInto(long timeout, Func function) + { + throw new NotSupportedException(); + } + + public virtual O AndThenInto(Func function) + { + throw new NotSupportedException(); + } + + public virtual T Await() + { + throw new NotSupportedException(); + } + + public virtual T Await(long timeout) + { + throw new NotSupportedException(); + } + + public virtual void Failed() + { + throw new NotSupportedException(); + } + + public virtual ICompletes Otherwise(Func function) + { + throw new NotSupportedException(); + } + + public virtual ICompletes OtherwiseConsume(Action consumer) + { + throw new NotSupportedException(); + } + + public virtual ICompletes RecoverFrom(Func function) + { + throw new NotSupportedException(); + } + + public virtual ICompletes Repeat() + { + throw new NotSupportedException(); + } + + public override ICompletes With(O outcome) + { + HasInternalOutcomeSet = true; + InternalOutcome = outcome; + + if (!resultHolder.IsOfSameGenericType()) + { + resultHolder = new ResultCompletes(InternalClientCompletes, InternalOutcome, HasInternalOutcomeSet); + } + + return (ICompletes)resultHolder; + } + + public override ICompletes ClientCompletes() + { + return InternalClientCompletes; + } + + public override void Reset(ICompletes clientCompletes) + { + InternalClientCompletes = clientCompletes; + InternalOutcome = null; + HasInternalOutcomeSet = false; + } + + public override bool IsOfSameGenericType() + => typeof(T) == typeof(TOtherType); + } +} diff --git a/src/Vlingo.Actors/RoundRobinRoutingStrategy.cs b/src/Vlingo.Actors/RoundRobinRoutingStrategy.cs new file mode 100644 index 00000000..a64d2922 --- /dev/null +++ b/src/Vlingo.Actors/RoundRobinRoutingStrategy.cs @@ -0,0 +1,33 @@ +// Copyright (c) 2012-2018 Vaughn Vernon. All rights reserved. +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. If a copy of the MPL +// was not distributed with this file, You can obtain +// one at https://mozilla.org/MPL/2.0/. + +using System.Collections.Generic; + +namespace Vlingo.Actors +{ + /// + /// RoundRobinRoutingStrategy is a that + /// treats its pool of IList<> routees as if it were a + /// circular linked list and which includes each routee, in turn, + /// in the . + /// + public class RoundRobinRoutingStrategy : RoutingStrategyAdapter + { + private int lastIndex; + + public RoundRobinRoutingStrategy() + { + lastIndex = 0; + } + + protected override Routing ChooseRouteFor(IList routees) + { + var nextIndex = lastIndex++ % routees.Count; + return Routing.With(routees[nextIndex]); + } + } +} diff --git a/src/Vlingo.Actors/Routee.cs b/src/Vlingo.Actors/Routee.cs index 4ca0f9bf..68db78b5 100644 --- a/src/Vlingo.Actors/Routee.cs +++ b/src/Vlingo.Actors/Routee.cs @@ -10,6 +10,9 @@ namespace Vlingo.Actors { + /// + /// Routee represents a potential target for for a routed message. + /// public class Routee { private readonly Actor actor; diff --git a/src/Vlingo.Actors/Router.cs b/src/Vlingo.Actors/Router.cs index 68429d44..144c91b0 100644 --- a/src/Vlingo.Actors/Router.cs +++ b/src/Vlingo.Actors/Router.cs @@ -9,6 +9,11 @@ namespace Vlingo.Actors { + /// + /// Router is a kind of that forwards a message + /// to zero or more other actors according to a + /// that is computed by a . + /// public abstract class Router : Actor { private readonly IList routees; @@ -16,7 +21,7 @@ public abstract class Router : Actor protected internal Router(RouterSpecification specification, IRoutingStrategy routingStrategy) { - for (int i = 0; i < specification.poolSize(); i++) + for (int i = 0; i < specification.PoolSize; i++) { ChildActorFor(specification.RouterDefinition, specification.RouterProtocol); } diff --git a/src/Vlingo.Actors/RouterSpecification.cs b/src/Vlingo.Actors/RouterSpecification.cs index 376b7efb..2fb558fc 100644 --- a/src/Vlingo.Actors/RouterSpecification.cs +++ b/src/Vlingo.Actors/RouterSpecification.cs @@ -9,6 +9,11 @@ namespace Vlingo.Actors { + /// + /// RouterSpecification specifies the definition and protocol of + /// the to which a will route, + /// as well as other details such as pool size. + /// public class RouterSpecification { private readonly int poolSize; //TODO: refactor towards resizable pool diff --git a/src/Vlingo.Actors/Routing.cs b/src/Vlingo.Actors/Routing.cs index 2a602d19..2df6c092 100644 --- a/src/Vlingo.Actors/Routing.cs +++ b/src/Vlingo.Actors/Routing.cs @@ -12,6 +12,11 @@ namespace Vlingo.Actors { + /// + /// Routing is an ordered sequence of that + /// was computed by a and whose elements + /// will be the target of a message forwarded by a . + /// public class Routing { public static Routing Empty() => new Routing(); @@ -41,7 +46,7 @@ internal Routing(IList routees) public virtual IList RouteesAs() => routees.Select(r => r.As()).ToList(); - public virtual bool IsEmpty => routees.Count > 0; + public virtual bool IsEmpty => routees.Count == 0; public override string ToString() => $"Routing[routees={routees}]"; diff --git a/src/Vlingo.Actors/RoutingStrategyAdapter.cs b/src/Vlingo.Actors/RoutingStrategyAdapter.cs index 08de386a..eede91d2 100644 --- a/src/Vlingo.Actors/RoutingStrategyAdapter.cs +++ b/src/Vlingo.Actors/RoutingStrategyAdapter.cs @@ -5,10 +5,15 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. +using System; using System.Collections.Generic; namespace Vlingo.Actors { + /// + /// RoutingStrategyAdapter provides default implementations of the methods + /// declared by . + /// public abstract class RoutingStrategyAdapter : IRoutingStrategy { public virtual Routing ChooseRouteFor(T1 routable1, IList routees) @@ -26,6 +31,7 @@ public virtual Routing ChooseRouteFor(T1 routable1, T2 routable2 public virtual Routing ChooseRouteFor(T1 routable1, T2 routable2, T3 routable3, T4 routable4, T5 routable5, IList routees) => ChooseRouteFor(routees); - protected abstract Routing ChooseRouteFor(IList routees); + protected virtual Routing ChooseRouteFor(IList routees) + => throw new NotImplementedException($"{GetType().FullName} must implement ChooseRouteFor(IList)"); } } diff --git a/src/Vlingo.Actors/Scheduled__Proxy.cs b/src/Vlingo.Actors/Scheduled__Proxy.cs index 584fe39f..31888dd9 100644 --- a/src/Vlingo.Actors/Scheduled__Proxy.cs +++ b/src/Vlingo.Actors/Scheduled__Proxy.cs @@ -26,7 +26,7 @@ public void IntervalSignal(IScheduled scheduled, object data) { if (!actor.IsStopped) { - Action consumer = actor => actor.IntervalSignal(scheduled, data); + Action consumer = x => x.IntervalSignal(scheduled, data); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, RepresentationIntervalSignal1); diff --git a/src/Vlingo.Actors/SmallestMailboxRoutingStrategy.cs b/src/Vlingo.Actors/SmallestMailboxRoutingStrategy.cs index 1b089fcd..e81f5426 100644 --- a/src/Vlingo.Actors/SmallestMailboxRoutingStrategy.cs +++ b/src/Vlingo.Actors/SmallestMailboxRoutingStrategy.cs @@ -9,6 +9,14 @@ namespace Vlingo.Actors { + /// + /// SmallestMailboxRoutingStrategy is a that + /// includes the pooled with the fewest pending messages + /// in its in the . By default, the + /// first encountered that has zero pending messages will + /// be chosen. Otherwise, the with the fewest pending + /// messages will be chosen. + /// public class SmallestMailboxRoutingStrategy : RoutingStrategyAdapter { protected override Routing ChooseRouteFor(IList routees) diff --git a/src/Vlingo.Actors/Stage.cs b/src/Vlingo.Actors/Stage.cs index 1fd5ab53..b7f63470 100644 --- a/src/Vlingo.Actors/Stage.cs +++ b/src/Vlingo.Actors/Stage.cs @@ -22,7 +22,12 @@ public class Stage : IStoppable private readonly Scheduler scheduler; private AtomicBoolean stopped; - public Stage(World world, string name) + /// + /// Initializes the new Stage of the world and with name. (INTERNAL ONLY) + /// + /// The World parent of this Stage. + /// The string name of this Stage. + internal Stage(World world, string name) { World = world; Name = name; @@ -32,9 +37,21 @@ public Stage(World world, string name) stopped = new AtomicBoolean(false); } + /// + /// Answers the protocol type as the means to message the backing Actor. + /// + /// The protocol. + /// The Actor that implements the protocol + /// The as public T ActorAs(Actor actor) => ActorProxyFor(actor, actor.LifeCycle.Environment.Mailbox); + /// + /// Answers the protocol of the newly created Actor that implements the protocol. + /// + /// The protocol type. + /// The Definition used to initialize the newly created Actor. + /// The Actor as . public T ActorFor(Definition definition) => ActorFor( definition, @@ -42,6 +59,15 @@ public T ActorFor(Definition definition) definition.Supervisor, definition.LoggerOr(World.DefaultLogger)); + /// + /// Answers the protocol of the newly created Actor that implements the protocol and + /// that will be assigned the specific and . + /// + /// The protocol + /// The Definition used to initialize the newly created Actor. + /// The IAddress to assign to the newly created Actor. + /// The ILogger to assign to the newly created Actor. + /// The Actor as . public T ActorFor(Definition definition, IAddress address, ILogger logger) { var actorAddress = AllocateAddress(definition, address); @@ -58,6 +84,14 @@ public T ActorFor(Definition definition, IAddress address, ILogger logger) return actor.ProtocolActor; } + /// + /// Answers the protocol of the newly created Actor that implements the protocol and + /// that will be assigned the specific . + /// + /// The protocol. + /// The Definition used to initialize the newly created Actor. + /// The ILogger to assign to the newly created Actor. + /// The Actor as . public T ActorFor(Definition definition, ILogger logger) => ActorFor( definition, @@ -65,6 +99,14 @@ public T ActorFor(Definition definition, ILogger logger) definition.Supervisor, logger); + /// + /// Answers the protocol of the newly created Actor that implements the protocol and + /// that will be assigned the specific . + /// + /// The protocol. + /// The Definition used to initialize the newly created Actor. + /// The IAddress to assign to the newly created Actor. + /// The Actor as . public T ActorFor(Definition definition, IAddress address) { var actorAddress = AllocateAddress(definition, address); @@ -81,17 +123,42 @@ public T ActorFor(Definition definition, IAddress address) return actor.ProtocolActor; } - + /// + /// Answers a Protocols that provides one or more supported for the + /// newly created Actor according to . + /// + /// The Definition providing parameters to the Actor. + /// The array of protocol that the Actor supports. + /// A instance. public Protocols ActorFor(Definition definition, Type[] protocols) - => new Protocols(ActorProtocolFor( + { + var all = ActorProtocolFor( definition, protocols, definition.ParentOr(World.DefaultParent), definition.Supervisor, - definition.LoggerOr(World.DefaultLogger))); + definition.LoggerOr(World.DefaultLogger)); + + return new Protocols(ActorProtocolActor.ToActors(all)); + } + /// + /// Answers the ICompletes<T> that will eventually complete with the protocol + /// of the backing Actor of the given , or null if not found. + /// + /// The protocol supported by the backing Actor. + /// The IAddress of the Actor to find. + /// ICompletes<T> of the backing actor found by the address. null if not found. public ICompletes ActorOf(IAddress address) => directoryScanner.ActorOf(address); + /// + /// Answers the TestActor<T>, being the protocol, of the new created Actor that implements the protocol. + /// The TestActor<T> is specifically used for test scenarios and provides runtime access to the internal + /// Actor instance. Test-based Actor instances are backed by the synchronous TestMailbox. + /// + /// The protocol type. + /// the Definition used to initialize the newly created Actor. + /// public TestActor TestActorFor(Definition definition) { var redefinition = Definition.Has( @@ -120,6 +187,14 @@ public TestActor TestActorFor(Definition definition) } } + /// + /// Answers a Protocols that provides one or more supported for the + /// newly created Actor according to , that can be used for testing. + /// Test-based Actor instances are backed by the synchronous TestMailbox. + /// + /// The Definition providing parameters to the Actor. + /// The array of protocols that the Actor supports. + /// internal Protocols TestActorFor(Definition definition, Type[] protocols) { var redefinition = Definition.Has( @@ -140,8 +215,14 @@ internal Protocols TestActorFor(Definition definition, Type[] protocols) return new Protocols(ActorProtocolActor.ToTestActors(all, protocols)); } + /// + /// Gets the count of the number of Actor instances contained in this Stage. + /// public int Count => directory.Count; + /// + /// A debugging tool used to print information about the Actor instances contained in this Stage. + /// public void Dump() { var logger = World.DefaultLogger; @@ -152,15 +233,32 @@ public void Dump() } } + /// + /// Gets the name of this Stage. + /// public string Name { get; } + /// + /// Registers with this Stage the supervisor for the given . + /// + /// + /// public void RegisterCommonSupervisor(Type protocol, ISupervisor common) => commonSupervisors[protocol] = common; + /// + /// Gets the Scheduler of this Stage. + /// public Scheduler Scheduler => scheduler; + /// + /// Gets whether or not this Stage has been stopped or is in the process of stopping. + /// public bool IsStopped => stopped.Get(); + /// + /// Initiates the process of stopping this Stage. + /// public void Stop() { if (!stopped.CompareAndSet(false, true)) @@ -179,19 +277,51 @@ public void Stop() scheduler.Close(); } + /// + /// Gets the World instance of this Stage. + /// public World World { get; } + /// + /// Answers the protocol for the newly created Actor instance. (INTERNAL ONLY) + /// + /// The protocol of the Actor. + /// The definition of the Actor. + /// The Actor parent of this Actor. + /// The possible supervisor of this Actor. + /// The logger for this Actor. + /// internal T ActorFor(Definition definition, Actor parent, ISupervisor maybeSupervisor, ILogger logger) { var actor = ActorProtocolFor(definition, parent, null, null, maybeSupervisor, logger); return actor.ProtocolActor; } + /// + /// Answers the ActorProtocolActor<object>[] for the newly created Actor instance. (INTERNAL ONLY) + /// + /// The Definition of the Actor. + /// The protocols of the Actor. + /// The Actor parent of this Actor. + /// The possible supervisor of this Actor. + /// Teh logger for this Actor. + /// internal ActorProtocolActor[] ActorProtocolFor(Definition definition, Type[] protocols, Actor parent, ISupervisor maybeSupervisor, ILogger logger) { return ActorProtocolFor(definition, protocols, parent, null, null, maybeSupervisor, logger); } + /// + /// Answers the ActorProtocolActor for the newly created Actor instance. (INTERNAL ONLY) + /// + /// + /// + /// + /// + /// + /// + /// + /// internal ActorProtocolActor ActorProtocolFor( Definition definition, Actor parent, @@ -215,6 +345,17 @@ internal ActorProtocolActor ActorProtocolFor( } } + /// + /// Answers the ActorProtocolActor[] for the newly created Actor instance. (INTERNAL ONLY) + /// + /// + /// + /// + /// + /// + /// + /// + /// internal ActorProtocolActor[] ActorProtocolFor( Definition definition, Type[] protocols, @@ -237,12 +378,33 @@ internal ActorProtocolActor[] ActorProtocolFor( } } + /// + /// Answers the protocol proxy for this newly created Actor. (INTERNAL ONLY) + /// + /// The protocol of the Actor + /// The Actor instance that backs the proxy protocol + /// The Mailbox instance of this Actor + /// internal T ActorProxyFor(Actor actor, IMailbox mailbox) => ActorProxy.CreateFor(actor, mailbox); + /// + /// Answers the proxy for this newly created Actor. (INTERNAL ONLY) + /// + /// The protocol of the Actor + /// The Actor instance that backs the proxy protocol + /// The Mailbox instance of this Actor + /// internal object ActorProxyFor(Type protocol, Actor actor, IMailbox mailbox) => ActorProxy.CreateFor(protocol, actor, mailbox); + /// + /// Answers the object[] protocol proxies for this newly created Actor. (INTERNAL ONLY) + /// + /// The protocols of the Actor + /// The Actor instance that backs the proxy protocol + /// The Mailbox instance of this Actor + /// internal object[] ActorProxyFor(Type[] protocols, Actor actor, IMailbox mailbox) { var proxies = new object[protocols.Length]; @@ -255,6 +417,13 @@ internal object[] ActorProxyFor(Type[] protocols, Actor actor, IMailbox mailbox) return proxies; } + /// + /// Answers the common Supervisor for the given protocol or the defaultSupervisor if there is + /// no registered common Supervisor. (INTERNAL ONLY) + /// + /// The protocol of the supervisor. + /// The default Supervisor to be used if there is no registered common Supervisor. + /// internal ISupervisor CommonSupervisorOr(ISupervisor defaultSupervisor) { if(commonSupervisors.TryGetValue(typeof(T), out ISupervisor value)) @@ -265,14 +434,24 @@ internal ISupervisor CommonSupervisorOr(ISupervisor defaultSupervisor) return defaultSupervisor; } + /// + /// Answers my Directory instance. (INTERNAL ONLY) + /// internal Directory Directory => directory; - internal void HandleFailureOf(ISupervised supervised) + /// + /// Handles a failure by suspending the Actor and dispatching to the Supervisor. (INTERNAL ONLY) + /// + /// The Supervised instance, which is an Actor + internal void HandleFailureOf(ISupervised supervised) { supervised.Suspend(); supervised.Supervisor.Inform(supervised.Error, supervised); } + /// + /// Start the directory scan process in search for a given Actor instance. (INTERNAL ONLY) + /// internal void StartDirectoryScanner() { directoryScanner = ActorFor( @@ -280,6 +459,11 @@ internal void StartDirectoryScanner() Definition.Parameters(directory))); } + /// + /// Stop the given Actor and all its children. The Actor instance is first removed from + /// the Directory of this Stage. (INTERNAL ONLY) + /// + /// The Actor to stop. internal void Stop(Actor actor) { var removedActor = directory.Remove(actor.Address); @@ -290,12 +474,38 @@ internal void Stop(Actor actor) } } + /// + /// Answers an Address for an Actor. If maybeAddress is allocated answer it; otherwise + /// answer a newly allocated Address. (INTERNAL ONLY) + /// + /// The Definition of the newly created Actor + /// The possible address + /// private IAddress AllocateAddress(Definition definition, IAddress maybeAddress) => maybeAddress ?? World.AddressFactory.UniqueWith(definition.ActorName); + /// + /// Answers a Mailbox for an Actor. If maybeMailbox is allocated answer it; otherwise + /// answer a newly allocated Mailbox. (INTERNAL ONLY) + /// + /// the Definition of the newly created Actor + /// the Address allocated to the Actor + /// the possible Mailbox + /// private IMailbox AllocateMailbox(Definition definition, IAddress address, IMailbox maybeMailbox) => maybeMailbox ?? ActorFactory.ActorMailbox(this, address, definition); + /// + /// Answers a newly created Actor instance from the internal ActorFactory. (INTERNAL ONLY) + /// + /// + /// + /// + /// + /// + /// + /// + /// private Actor CreateRawActor( Definition definition, Actor parent, @@ -336,6 +546,9 @@ private Actor CreateRawActor( return actor; } + /// + /// Stops all Actor instances from the PrivateRootActor down to the last child. (INTERNAL ONLY) + /// private void Sweep() { if (World.PrivateRoot != null) @@ -345,6 +558,10 @@ private void Sweep() } } + /// + /// Internal type used to manage Actor proxy creation. (INTERNAL ONLY) + /// + /// The protocol type. internal class ActorProtocolActor { private readonly Actor actor; diff --git a/src/Vlingo.Actors/Startable__Proxy.cs b/src/Vlingo.Actors/Startable__Proxy.cs index b017b407..939bd835 100644 --- a/src/Vlingo.Actors/Startable__Proxy.cs +++ b/src/Vlingo.Actors/Startable__Proxy.cs @@ -22,7 +22,7 @@ public Startable__Proxy(Actor actor, IMailbox mailbox) public void Start() { - Action consumer = actor => actor.Start(); + Action consumer = x => x.Start(); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, "Start()"); diff --git a/src/Vlingo.Actors/Stoppable__Proxy.cs b/src/Vlingo.Actors/Stoppable__Proxy.cs index df87fab3..64f2ec79 100644 --- a/src/Vlingo.Actors/Stoppable__Proxy.cs +++ b/src/Vlingo.Actors/Stoppable__Proxy.cs @@ -26,7 +26,7 @@ public void Stop() { if (!actor.IsStopped) { - Action consumer = actor => actor.Stop(); + Action consumer = x => x.Stop(); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, "Stop()"); diff --git a/src/Vlingo.Actors/Stowage.cs b/src/Vlingo.Actors/Stowage.cs index bf9f1ffe..9d78df03 100644 --- a/src/Vlingo.Actors/Stowage.cs +++ b/src/Vlingo.Actors/Stowage.cs @@ -5,7 +5,9 @@ // was not distributed with this file, You can obtain // one at https://mozilla.org/MPL/2.0/. +using System; using System.Collections.Generic; +using System.Linq; using Vlingo.Common; namespace Vlingo.Actors @@ -75,7 +77,7 @@ internal void DispersingMode() dispersing = true; } - void Restow(Stowage other) + internal void Restow(Stowage other) { var message = Head; while (message != null) @@ -96,7 +98,9 @@ internal void Stow(IMessage message) } else { - toStow = new StowedLocalMessage((LocalMessage)message); + var closedMsgType = message.GetType().GetGenericArguments().First(); + var stowedLocalMsgType = typeof(StowedLocalMessage<>).MakeGenericType(closedMsgType); + toStow = (IMessage)Activator.CreateInstance(stowedLocalMsgType, message); } stowedMessages.Enqueue(toStow); diff --git a/src/Vlingo.Actors/Supervisor__Proxy.cs b/src/Vlingo.Actors/Supervisor__Proxy.cs index 2fa84e9a..afac6ab9 100644 --- a/src/Vlingo.Actors/Supervisor__Proxy.cs +++ b/src/Vlingo.Actors/Supervisor__Proxy.cs @@ -30,7 +30,7 @@ public void Inform(Exception error, ISupervised supervised) { if (!actor.IsStopped) { - Action consumer = actor => actor.Inform(error, supervised); + Action consumer = x => x.Inform(error, supervised); if (mailbox.IsPreallocated) { mailbox.Send(actor, consumer, null, RepresentationInform1); diff --git a/src/Vlingo.Actors/TestKit/TestActor.cs b/src/Vlingo.Actors/TestKit/TestActor.cs index ac9e787c..94d9b0fe 100644 --- a/src/Vlingo.Actors/TestKit/TestActor.cs +++ b/src/Vlingo.Actors/TestKit/TestActor.cs @@ -16,10 +16,15 @@ public TestActor(Actor actor, T protocol, IAddress address) Address = address; } - public T Actor { get; } - public IAddress Address { get; } - public Actor ActorInside { get; } + public virtual T Actor { get; } - public TestState ViewTestState() => ActorInside.ViewTestState(); + public virtual TActor ActorAs() + => (TActor)(object)Actor; + + public virtual IAddress Address { get; } + + public virtual Actor ActorInside { get; } + + public virtual TestState ViewTestState() => ActorInside.ViewTestState(); } } diff --git a/src/Vlingo.Actors/TestKit/TestUntil.cs b/src/Vlingo.Actors/TestKit/TestUntil.cs index 44a5c642..6c8b8f08 100644 --- a/src/Vlingo.Actors/TestKit/TestUntil.cs +++ b/src/Vlingo.Actors/TestKit/TestUntil.cs @@ -12,14 +12,14 @@ namespace Vlingo.Actors.TestKit { public class TestUntil : IDisposable { - private readonly CountdownEvent countDown; + private readonly CountdownEvent countDownEvent; private readonly bool zero; public static TestUntil Happenings(int times) => new TestUntil(count: times); public void CompleteNow() { - while (!countDown.IsSet) + while (!countDownEvent.IsSet) { Happened(); } @@ -42,7 +42,7 @@ public void Completes() { try { - countDown.Wait(); + countDownEvent.Wait(); } catch (Exception) { @@ -51,25 +51,59 @@ public void Completes() } } + public bool CompletesWithin(long timeout) + { + var countDown = timeout; + while (true) + { + if (countDownEvent.IsSet) + { + return true; + } + + try + { + Thread.Sleep(TimeSpan.FromMilliseconds((countDown >= 0 && countDown < 100) ? countDown : 100)); + } + catch (Exception) + { + } + + if (countDownEvent.IsSet) + { + return true; + } + + if (timeout >= 0) + { + countDown -= 100; + if (countDown <= 0) + { + return false; + } + } + } + } + public TestUntil Happened() { - if (!countDown.IsSet) + if (!countDownEvent.IsSet) { - countDown.Signal(); + countDownEvent.Signal(); } return this; } - public int Remaining => countDown.CurrentCount; + public int Remaining => countDownEvent.CurrentCount; - public override string ToString() => $"TestUntil[count={countDown.CurrentCount} , zero={zero}]"; + public override string ToString() => $"TestUntil[count={countDownEvent.CurrentCount} , zero={zero}]"; - public void Dispose() => countDown.Dispose(); + public void Dispose() => countDownEvent.Dispose(); private TestUntil(int count) { - countDown = new CountdownEvent(initialCount: count); + countDownEvent = new CountdownEvent(initialCount: count); zero = (count == 0); } diff --git a/src/Vlingo.Actors/TestKit/TestWorld.cs b/src/Vlingo.Actors/TestKit/TestWorld.cs index a9da4bc1..0bd6f348 100644 --- a/src/Vlingo.Actors/TestKit/TestWorld.cs +++ b/src/Vlingo.Actors/TestKit/TestWorld.cs @@ -27,10 +27,7 @@ internal static TestWorld Instance } } - private readonly IDictionary> actorMessages = new Dictionary>(); - - public IList AllMessagesFor(IAddress address) - => actorMessages.ContainsKey(address.Id) ? actorMessages[address.Id] : new List(); + private readonly IDictionary> actorMessages; public static TestWorld Start(string name) { @@ -69,16 +66,6 @@ public static TestWorld StartWithDefaults(string name) } } - public void Track(IMessage message) - { - var id = message.Actor.Address.Id; - if (!actorMessages.ContainsKey(id)) - { - actorMessages[id] = new List(); - } - actorMessages[id].Add(message); - } - public TestActor ActorFor(Definition definition) { if (World.IsTerminated) @@ -98,6 +85,25 @@ public Protocols ActorFor(Definition definition, Type[] protocols) return World.Stage.TestActorFor(definition, protocols); } + public IList AllMessagesFor(IAddress address) + { + if(actorMessages.TryGetValue(address.Id, out var all)) + { + return all; + } + + return new List(); + } + + public void Close() + { + if (!IsTerminated) + { + Terminate(); + } + } + + public void ClearTrackedMessages() => actorMessages.Clear(); public ILogger DefaultLogger => World.DefaultLogger; @@ -116,19 +122,26 @@ public void Terminate() actorMessages.Clear(); } + public void Track(IMessage message) + { + var id = message.Actor.Address.Id; + if (!actorMessages.ContainsKey(id)) + { + actorMessages[id] = new List(); + } + + actorMessages[id].Add(message); + } + public World World { get; } public IMailboxProvider MailboxProvider { get; } - public void ClearTrackedMessages() - { - actorMessages.Clear(); - } - private TestWorld(World world, string name) { World = world; MailboxProvider = new TestMailboxPlugin(World); + actorMessages = new Dictionary>(); Instance = this; } diff --git a/src/Vlingo.Actors/Vlingo.Actors.csproj b/src/Vlingo.Actors/Vlingo.Actors.csproj index 37602b69..8d61b853 100644 --- a/src/Vlingo.Actors/Vlingo.Actors.csproj +++ b/src/Vlingo.Actors/Vlingo.Actors.csproj @@ -1,10 +1,10 @@  - netcoreapp2.0 + netstandard2.0 true - 0.1.1 + 0.2.0 Vlingo.Actors Vlingo @@ -17,6 +17,11 @@ https://github.com/vlingo-net/vlingo-net-actors - + + <_Parameter1>Vlingo.Actors.Tests + + + + \ No newline at end of file diff --git a/src/Vlingo.Actors/World.cs b/src/Vlingo.Actors/World.cs index abcecf91..b50e5f3d 100644 --- a/src/Vlingo.Actors/World.cs +++ b/src/Vlingo.Actors/World.cs @@ -6,12 +6,20 @@ // one at https://mozilla.org/MPL/2.0/. using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using Vlingo.Actors.Plugin.Completes; +using Vlingo.Actors.Plugin.Logging; +using Vlingo.Actors.Plugin.Mailbox; using Vlingo.Common; using Vlingo.Common.Compiler; namespace Vlingo.Actors { + /// + /// The World of the actor runtime through which all Stage and Actor instances are created and run. + /// All plugins and all default facilities are registered through the World. + /// public sealed class World : IRegistrar { internal const long PrivateRootId = long.MaxValue; @@ -33,6 +41,11 @@ public sealed class World : IRegistrar private ILogger defaultLogger; private ISupervisor defaultSupervisor; + /// + /// Initializes the new World instance with the given name and configuration. + /// + /// The string name to assign to this World. + /// the Configuration to use to initialize various World facilities. private World(string name, Configuration configuration) { Name = name; @@ -41,7 +54,8 @@ private World(string name, Configuration configuration) completesProviderKeeper = new DefaultCompletesEventuallyProviderKeeper(); loggerProviderKeeper = new DefaultLoggerProviderKeeper(); mailboxProviderKeeper = new DefaultMailboxProviderKeeper(); - stages = new Dictionary(); + stages = new ConcurrentDictionary(); + dynamicDependencies = new ConcurrentDictionary(); var defaultStage = StageNamed(DefaultStage); @@ -54,39 +68,74 @@ private World(string name, Configuration configuration) defaultStage.StartDirectoryScanner(); } + /// + /// Gets the Address Factory for this World. + /// public IAddressFactory AddressFactory { get; } + /// + /// Gets the Configuration of this World + /// public Configuration Configuration { get; } + /// + /// Answers a new World with the given and that is configured with + /// the contents of the vlingo-actors.properties file. + /// + /// the string name to assign to the new World instance. + /// A World instance. public static World Start(string name) { return Start(name, Properties.Instance); } + /// + /// Answers a new World with the given and that is configured with + /// the contents of the . + /// + /// The string name to assign to the new World instance. + /// The used for configuration. + /// A World instance. public static World Start(string name, Properties properties) { return Start(name, Configuration.DefineWith(properties)); } - private static readonly object startMutex = new object(); + /// + /// Answers a new World with the given and that is configured with + /// the contents of the . + /// + /// The string name to assign to the new World instance. + /// The used for configuration + /// A World instance. public static World Start(string name, Configuration configuration) { - lock (startMutex) + if (name == null) { - if (name == null) - { - throw new ArgumentException("The world name must not be null."); - } - - return new World(name, configuration); + throw new ArgumentException("The world name must not be null."); } + + return new World(name, configuration); } + /// + /// Answers a new World with the given and that is configured with + /// the contents of the default of sensible settings. + /// + /// The string name to assign to the new World instance. + /// A World instance. public static World StartWithDefault(string name) { return Start(name, Configuration.Define()); } + /// + /// Answers a new concrete Actor that is defined by the parameters of + /// and supports the protocol defined by protocol. + /// + /// The protocol type. + /// The Definition providing parameters to theActor. + /// public T ActorFor(Definition definition) { if (IsTerminated) @@ -97,6 +146,13 @@ public T ActorFor(Definition definition) return Stage.ActorFor(definition); } + /// + /// Answers a Protocols that provides one or more supported for the + /// newly created Actor according to . + /// + /// The Definition providing parameters to theActor. + /// The array of protocols that the Actor supports. + /// public Protocols ActorFor(Definition definition, Type[] protocols) { if (IsTerminated) @@ -107,11 +163,27 @@ public Protocols ActorFor(Definition definition, Type[] protocols) return Stage.ActorFor(definition, protocols); } + /// + /// Gets the dead-letters of this World, which is backed + /// by an Actor. Interested parties may register for notifications + /// as a via protocol. + /// public IDeadLetters DeadLetters { get; internal set; } - public ICompletesEventually CompletesFor(ICompletes clientCompletes) + /// + /// Answers a new instance that backs the . + /// This manages the ICompletes using the CompletesEventually plugin Actor pool. + /// + /// The allocated for eventual completion of clientCompletes + /// + public ICompletesEventually CompletesFor(ICompletes clientCompletes) => completesProviderKeeper.FindDefault().ProvideCompletesFor(clientCompletes); + /// + /// Gets the default ILogger that is registered with this World. The + /// ILogger protocol is implemented by an Actor such that all logging is + /// asynchronous. + /// public ILogger DefaultLogger { get @@ -138,8 +210,17 @@ public ILogger DefaultLogger } } + /// + /// Gets the Actor that serves as the default parent for this World. + /// Unless overridden using Configuration (e.g. Properties or fluent Configuration)s + /// public Actor DefaultParent { get; private set; } + /// + /// Answers the ISupervisor protocol for sending messages to the default supervisor. + /// Unless overridden using Configuration (e.g. Properties or fluent Configuration)s + /// The default supervisor is the single PublicRootActor. + /// public ISupervisor DefaultSupervisor { get @@ -153,16 +234,35 @@ public ISupervisor DefaultSupervisor } } + /// + /// Answers the ILogger named with , or null if it does not exist. + /// + /// The string name of the logger. + /// public ILogger Logger(string name) => loggerProviderKeeper.FindNamed(name).Logger; + /// + /// Gets the string name of this World. + /// public string Name { get; } + /// + /// Registers the ICompletesEventuallyProvider plugin by . + /// + /// The string name of the ICompletesEventuallyProvider to register. + /// The ICompletesEventuallyProvider to register. public void Register(string name, ICompletesEventuallyProvider completesEventuallyProvider) { completesEventuallyProvider.InitializeUsing(Stage); completesProviderKeeper.Keep(name, completesEventuallyProvider); } + /// + /// Registers the ILoggerProvider plugin by . + /// + /// The string of the logger provider. + /// The bool value indicating whether this is the default logger provider. + /// The ILoggerProvider to register. public void Register(string name, bool isDefault, ILoggerProvider loggerProvider) { var actualDefault = loggerProviderKeeper.FindDefault() == null ? true : isDefault; @@ -170,9 +270,22 @@ public void Register(string name, bool isDefault, ILoggerProvider loggerProvider defaultLogger = loggerProviderKeeper.FindDefault().Logger; } + /// + /// Registers the IMailboxProvider plugin by . + /// + /// The string name of the mailbox provider to register. + /// The bool value indicating whether this is the default mailbox provider. + /// The IMailboxProvider to register. public void Register(string name, bool isDefault, IMailboxProvider mailboxProvider) => mailboxProviderKeeper.Keep(name, isDefault, mailboxProvider); + /// + /// Registers the plugin by that will supervise all Actor that implement the . + /// + /// The string of the Stage in which the is be registered. + /// The string name of the supervisor to register. + /// The protocol for which the supervisor will supervise. + /// The Type (which should be a subclass of Actor) to register as a supervisor. public void RegisterCommonSupervisor(string stageName, string name, Type supervisedProtocol, Type supervisorClass) { try @@ -188,6 +301,13 @@ public void RegisterCommonSupervisor(string stageName, string name, Type supervi } } + /// + /// Registers the plugin by that will serve as the default supervise for all Actor + /// that are not supervised by a specific supervisor. + /// + /// The string of the Stage in which the is be registered. + /// The string name of the supervisor to register. + /// The Type (which should be a subclass of Actor) to register as a supervisor. public void RegisterDefaultSupervisor(string stageName, string name, Type supervisorClass) { try @@ -204,6 +324,10 @@ public void RegisterDefaultSupervisor(string stageName, string name, Type superv } } + /// + /// Registers the ICompletesEventuallyProviderKeeper plugin. + /// + /// The ICompletesEventuallyProviderKeeper to register. public void RegisterCompletesEventuallyProviderKeeper(ICompletesEventuallyProviderKeeper keeper) { if (this.completesProviderKeeper != null) @@ -214,6 +338,10 @@ public void RegisterCompletesEventuallyProviderKeeper(ICompletesEventuallyProvid this.completesProviderKeeper = keeper; } + /// + /// Registers the ILoggerProviderKeeper plugin. + /// + /// The ILoggerProviderKeeper to register. public void RegisterLoggerProviderKeeper(ILoggerProviderKeeper keeper) { if (this.loggerProviderKeeper != null) @@ -223,6 +351,10 @@ public void RegisterLoggerProviderKeeper(ILoggerProviderKeeper keeper) this.loggerProviderKeeper = keeper; } + /// + /// Registers the IMailboxProviderKeeper plugin. + /// + /// The IMailboxProviderKeeper to register. public void RegisterMailboxProviderKeeper(IMailboxProviderKeeper keeper) { if (this.mailboxProviderKeeper != null) @@ -232,19 +364,39 @@ public void RegisterMailboxProviderKeeper(IMailboxProviderKeeper keeper) this.mailboxProviderKeeper = keeper; } + /// + /// Registers the dynamic dependencies by . + /// + /// The string name of the dynamic dependency. + /// The dependency object to register. public void RegisterDynamic(string name, object dep) { this.dynamicDependencies[name] = dep; } + /// + /// Answers the instance of the named dependency. + /// + /// The dependecy type. + /// The string name of dynamic dependency. + /// public TDependency ResolveDynamic(string name) { return (TDependency)this.dynamicDependencies[name]; } + /// + /// Gets the default Stage, which is the Stage created when this World was started. + /// public Stage Stage => StageNamed(DefaultStage); private readonly object stageNamedMutex = new object(); + /// + /// Answers the Stage named by , or the newly created Stage instance named by + /// if the {@code Stage} does not already exist. + /// + /// The string name of the Stage to answer. + /// public Stage StageNamed(string name) { lock (stageNamedMutex) @@ -263,8 +415,14 @@ public Stage StageNamed(string name) } } + /// + /// Gets whether or not this World has been terminated or is in the process of termination. + /// public bool IsTerminated => Stage.IsStopped; + /// + /// Initiates the World terminate process if the process has not already been initiated. + /// public void Terminate() { if (!IsTerminated) @@ -280,11 +438,26 @@ public void Terminate() } } + /// + /// Answers this World instance. + /// World IRegistrar.World => this; + /// + /// Answers the IMailbox instance by and . (INTERNAL ONLY) + /// + /// The string name of the IMailbox type to use. + /// The int hash code to help determine which IMailbox instance to assign. + /// internal IMailbox AssignMailbox(string mailboxName, int hashCode) => mailboxProviderKeeper.AssignMailbox(mailboxName, hashCode); + /// + /// Answers a name for a IMailbox given a , which if non-existing + /// the name of the default mailbox is answered. (INTERNAL ONLY) + /// + /// the string name of the desired IMailbox + /// internal string MailboxNameFrom(string candidateMailboxName) { if (candidateMailboxName == null) @@ -301,12 +474,20 @@ internal string MailboxNameFrom(string candidateMailboxName) } } - internal String FindDefaultMailboxName() + /// + /// Get the name of the default mailbox. (INTERNAL ONLY) + /// + /// + internal string FindDefaultMailboxName() { return mailboxProviderKeeper.FindDefault(); } private readonly object defaultParentMutex = new object(); + /// + /// Sets the Actor as the default for this World. (INTERNAL ONLY) + /// + /// The Actor to use as default parent. internal void SetDefaultParent(Actor defaultParent) { lock (defaultParentMutex) @@ -321,6 +502,10 @@ internal void SetDefaultParent(Actor defaultParent) } private readonly object deadLettersMutex = new object(); + /// + /// Sets the as the default for this World. (INTERNAL ONLY) + /// + /// The IDeadLetters to use as the default. internal void SetDeadLetters(IDeadLetters deadLetters) { lock (deadLettersMutex) @@ -335,9 +520,16 @@ internal void SetDeadLetters(IDeadLetters deadLetters) } } + /// + /// Gets the PrivateRootActor instance as a IStoppable. (INTERNAL ONLY) + /// internal IStoppable PrivateRoot { get; private set; } private readonly object privateRootMutex = new object(); + /// + /// Sets the PrivateRootActor instances as a IStoppable. (INTERNAL ONLY) + /// + /// The IStoppable protocol backed by the PrivateRootActor internal void SetPrivateRoot(IStoppable privateRoot) { lock (privateRootMutex) @@ -352,9 +544,16 @@ internal void SetPrivateRoot(IStoppable privateRoot) } } + /// + /// Gets the PublicRootActor instance as a IStoppable. (INTERNAL ONLY) + /// internal IStoppable PublicRoot { get; private set; } public readonly object publicRootMutex = new object(); + /// + /// Sets the PublicRootActor instances as a IStoppable. (INTERNAL ONLY) + /// + /// The IStoppable protocol backed by the PublicRootActor internal void SetPublicRoot(IStoppable publicRoot) { lock (publicRootMutex) @@ -368,6 +567,12 @@ internal void SetPublicRoot(IStoppable publicRoot) } } + /// + /// Starts the PrivateRootActor. When the PrivateRootActor starts it will in turn + /// start the PublicRootActor. + /// + /// The Stage in which to start the PrivateRootActor. + /// The default ILogger for this World and Stage. private void StartRootFor(Stage stage, ILogger logger) => stage.ActorProtocolFor( Definition.Has(Definition.NoParameters, PrivateRootName),