All Projects → OrleansContrib → Orleans.SyncWork

OrleansContrib / Orleans.SyncWork

Licence: MIT license
This package's intention is to expose an abstract base class to allow https://github.com/dotnet/orleans/ to work with long running CPU bound synchronous work, without becoming overloaded.

Programming Languages

C#
18002 projects

Projects that are alternatives of or similar to Orleans.SyncWork

road-to-orleans
This repository illustrates the road to orleans with practical, real-life examples. From most basic, to more advanced techniques.
Stars: ✭ 55 (+77.42%)
Mutual labels:  orleans, orleans-grains
Orleans.Providers.Redis
Redis stream provider for Microsoft Orleans
Stars: ✭ 15 (-51.61%)
Mutual labels:  orleans
Orleans.providers.mongodb
A MongoDb implementation of the Orleans Providers: Membership, Storage and Reminders.
Stars: ✭ 51 (+64.52%)
Mutual labels:  orleans
Templates
.NET project templates with batteries included, providing the minimum amount of code required to get you going faster.
Stars: ✭ 2,864 (+9138.71%)
Mutual labels:  orleans
Ocelot.orleanshttpgateway
Orleans can use Ocelot Api Gateway
Stars: ✭ 67 (+116.13%)
Mutual labels:  orleans
Orleans.CosmosDB
Orleans providers for Azure Cosmos DB
Stars: ✭ 36 (+16.13%)
Mutual labels:  orleans
Orleans
Orleans is a cross-platform framework for building distributed applications with .NET
Stars: ✭ 8,131 (+26129.03%)
Mutual labels:  orleans
Phenix.NET7
Phenix Framework 7 for .NET 6
Stars: ✭ 31 (+0%)
Mutual labels:  orleans
Announcements
📢 Orleans related public announcements - watch this repo to stay up-to-date
Stars: ✭ 35 (+12.9%)
Mutual labels:  orleans
Signalr.orleans
SignalR backend based on Orleans.
Stars: ✭ 156 (+403.23%)
Mutual labels:  orleans
Fluentdispatch
🌊 .NET Standard 2.1 framework which makes easy to scaffold distributed systems and dispatch incoming load into units of work in a deterministic way.
Stars: ✭ 152 (+390.32%)
Mutual labels:  orleans
Microdot
Microdot: An open source .NET microservices framework
Stars: ✭ 1,222 (+3841.94%)
Mutual labels:  orleans
Orleans.Http
An HTTP API for Microsoft Orleans
Stars: ✭ 99 (+219.35%)
Mutual labels:  orleans
Orleans.activities
Workflow Foundation (.Net 4.x System.Activities workflows) over Microsoft Orleans framework, providing stable, long-running, extremely scalable processes with XAML designer support.
Stars: ✭ 61 (+96.77%)
Mutual labels:  orleans
serverless-orleans
A demonstration of local development and debugging + serverless Azure deployment of a Dockerized Orleans application.
Stars: ✭ 21 (-32.26%)
Mutual labels:  orleans
Orleanstestkit
Unit Test Toolkit for Microsoft Orleans
Stars: ✭ 42 (+35.48%)
Mutual labels:  orleans
Orleans.clustering.kubernetes
Orleans Membership provider for Kubernetes
Stars: ✭ 140 (+351.61%)
Mutual labels:  orleans
Erleans
Erlang Orleans
Stars: ✭ 239 (+670.97%)
Mutual labels:  orleans
GeekServer
基于.Netcore的开发效率高,性能强,跨平台,持久化层透明,支持不停服热更新的游戏服务器。Best for your unity game server!
Stars: ✭ 171 (+451.61%)
Mutual labels:  orleans
meetups
📆 A repository to organise virtual meetups to discuss Orleans and other distributed systems programming on .NET
Stars: ✭ 79 (+154.84%)
Mutual labels:  orleans

Build and test Coverage Status

Latest NuGet Version License

This package's intention is to expose an abstract base class to allow Orleans to work with long running, CPU bound, synchronous work, without becoming overloaded.

Built with an open source license, thanks Jetbrains!

Building

The project is built primarily with .net6 in mind, though the test project is currently targeting netcoreapp3.1, and .net5.

Requirements

Project Overview

There are several projects within this repository, all with the idea of demonstrating and/or testing the claim that the NuGet package https://www.nuget.org/packages/Orleans.SyncWork/ does what it is claimed it does.

The projects in this repository include:

Orleans.SyncWork

The meat and potatoes of the project. This project contains the abstraction of "Long Running, CPU bound, Synchronous work" in the form of an abstract base class SyncWorker; which implements an interface ISyncWorker.

When long running work is identified, you can extend the base class SyncWorker, providing a TRequest and TResponse unique to the long running work. This allows you to create as many ISyncWork<TRequest, TResponse> implementations as necessary, for all your long running CPU bound needs! (At least that is the hope.)

Basic "flow" of the SyncWork:

  • Start
  • Poll GetStatus until a Completed or Faulted status is received
  • GetResult or GetException depending on the GetStatus

This package introduces a few "requirements" against Orleans:

  • In order to not overload Orleans, a LimitedConcurrencyLevelTaskScheduler is introduced. This task scheduler is registered (either manually or through the provided extension method) with a maximum level of concurrency for the silo being set up. This maximum concurrency MUST allow for idle threads, lest the Orleans server be overloaded. In testing, the general rule of thumb was Environment.ProcessorCount - 2 max concurrency. The important part is that the CPU is not fully "tapped out" such that the normal Orleans asynchronous messaging can't make it through due to the blocking sync work - this will make things start timing out.

  • Blocking grains are stateful, and are currently keyed on a Guid. If in a situation where multiple grains of long running work is needed, each grain should be initialized with its own unique identity.

  • Blocking grains likely CAN NOT dispatch further blocking grains. This is not yet tested under the repository, but it stands to reason that with a limited concurrency scheduler, the following scenario would lead to a deadlock:

    • Grain A is long running
    • Grain B is long running
    • Grain A initializes and fires off Grain B
    • Grain A cannot complete its work until it gets the results of Grain B

    In the above scenario, if "Grain A" is "actively being worked" and it fires off a "Grain B", but "Grain A" cannot complete its work until "Grain B" finishes its own, but "Grain B" cannot start its work until "Grain A" finishes its work due to limited concurrency, you've run into a situation where the limited concurrency task scheduler can never finish the work of "Grain A".

    That was quite a sentence, hopefully the point was conveyed somewhat sensibly. There may be a way to avoid the above scenario, but I have not yet deeply explored it.

Usage

Extend the base class to implement a long running grain.

public class PasswordVerifier : SyncWorker<PasswordVerifierRequest, PasswordVerifierResult>, IGrain
{
    private readonly IPasswordVerifier _passwordVerifier;

    public PasswordVerifier(
        ILogger<PasswordVerifier> logger, 
        LimitedConcurrencyLevelTaskScheduler limitedConcurrencyLevelTaskScheduler, 
        IPasswordVerifier passwordVerifier) : base(logger, limitedConcurrencyLevelTaskScheduler)
    {
        _passwordVerifier = passwordVerifier;
    }

    protected override async Task<PasswordVerifierResult> PerformWork(PasswordVerifierRequest request)
    {
        var verifyResult = await _passwordVerifier.VerifyPassword(request.PasswordHash, request.Password);

        return new PasswordVerifierResult()
        {
            IsValid = verifyResult
        };
    }
}
public class PasswordVerifierRequest
{
    public string Password { get; set; }
    public string PasswordHash { get; set; }
}

public class PasswordVerifierResult
{
    public bool IsValid { get; set; }
}

Run the grain:

var request = new PasswordVerifierRequest()
{
    Password = "my super neat password that's totally secure because it's super long",
    PasswordHash = "$2a$11$vBzJ4Ewx28C127AG5x3kT.QCCS8ai0l4JLX3VOX3MzHRkF4/A5twy"
}
var passwordVerifyGrain = grainFactory.GetGrain<ISyncWorker<PasswordVerifierRequest, PasswordVerifierResult>>(Guid.NewGuid());
var result = await passwordVerifyGrain.StartWorkAndPollUntilResult(request);

The above StartWorkAndPollUntilResult is an extension method defined in the package (SyncWorkerExtensions) that Starts, Polls, and finally GetResult or GetException upon completed work. There would seemingly be place for improvement here as it relates to testing unexpected scenarios, configuration based polling, etc.

Orleans.SyncWork.Tests

Unit testing project for the work in Orleans.SyncWork. These tests bring up a "TestCluster" which is used for the full duration of the tests against the grains.

One of the tests in particular throws 10k grains onto the cluster at once, all of which are long running (~200ms each) on my machine - more than enough time to overload the cluster if the limited concurrency task scheduler is not working along side the SyncWork base class correctly.

TODO: still could use a few more unit tests here to if nothing else, document behavior.

Orleans.SyncWork.Demo.Api

This is a demo of the ISyncWork<TRequest, TResult> in action. This project is being used as both a Orleans Silo, and client. Generally you would stand up nodes to the cluster separate from the clients against the cluster. Since we have only one node for testing purposes, this project acts as both the silo host and client.

The OrleansDashboard is also brought up with the API. You can see an example of hitting an endpoint in which 10k password verification requests are received here:

Dashboard showing 10k CPU bound, long running requests

Swagger UI is also made available to the API for testing out the endpoints for demo purposes.

Orleans.SyncWork.Demo.Api.Benchmark

Utilizing Benchmark DotNet, a benchmarking class was created to both test that the cluster wasn't falling over, and see what sort of timing situation we're dealing with.

Following is the benchmark used at the time of writing:

public class Benchy
{
    const int TotalNumberPerBenchmark = 100;
    private readonly IPasswordVerifier _passwordVerifier = new Services.PasswordVerifier();
    private readonly PasswordVerifierRequest _request = new PasswordVerifierRequest()
    {
        Password = PasswordConstants.Password,
        PasswordHash = PasswordConstants.PasswordHash
    };

    [Benchmark]
    public void Serial()
    {
        for (var i = 0; i < TotalNumberPerBenchmark; i++)
        {
            _passwordVerifier.VerifyPassword(PasswordConstants.PasswordHash, PasswordConstants.Password);
        }
    }

    [Benchmark]
    public async Task MultipleTasks()
    {
        var tasks = new List<Task>();
        for (var i = 0; i < TotalNumberPerBenchmark; i++)
        {
            tasks.Add(_passwordVerifier.VerifyPassword(PasswordConstants.PasswordHash, PasswordConstants.Password));
        }

        await Task.WhenAll(tasks);
    }

    [Benchmark]
    public async Task MultipleParallelTasks()
    {
        var tasks = new List<Task>();

        Parallel.For(0, TotalNumberPerBenchmark, i =>
        {
            tasks.Add(_passwordVerifier.VerifyPassword(PasswordConstants.PasswordHash, PasswordConstants.Password));
        });

        await Task.WhenAll(tasks);
    }

    [Benchmark]
    public async Task OrleansTasks()
    {
        var siloHost = await BenchmarkingSIloHost.GetSiloHost();
        var grainFactory = siloHost.Services.GetRequiredService<IGrainFactory>();
        var tasks = new List<Task>();
        for (var i = 0; i < TotalNumberPerBenchmark; i++)
        {
            var grain = grainFactory.GetGrain<ISyncWorker<PasswordVerifierRequest, PasswordVerifierResult>>(Guid.NewGuid());
            tasks.Add(grain.StartWorkAndPollUntilResult(_request));
        }

        await Task.WhenAll(tasks);
    }
}

And here are the results:

Method Mean Error StdDev
Serial 12.399 s 0.0087 s 0.0077 s
MultipleTasks 12.289 s 0.0106 s 0.0094 s
MultipleParallelTasks 1.749 s 0.0347 s 0.0413 s
OrleansTasks 2.130 s 0.0055 s 0.0084 s

And of course note, that in the above the Orleans tasks are limited to my local cluster. In a more real situation where you have multiple nodes to the cluster, you could expect to get better timing, though you'd probably have to deal more with network latency.

Orleans.SyncWork.Demo.Services

This project defines several grains to demonstrate the workings of the Orleans.SyncWork package, through the Web API, benchmark, and tests.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].