Orleans.SyncWork 0.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package Orleans.SyncWork --version 0.0.1
NuGet\Install-Package Orleans.SyncWork -Version 0.0.1
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Orleans.SyncWork" Version="0.0.1" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Orleans.SyncWork --version 0.0.1
#r "nuget: Orleans.SyncWork, 0.0.1"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Orleans.SyncWork as a Cake Addin
#addin nuget:?package=Orleans.SyncWork&version=0.0.1

// Install Orleans.SyncWork as a Cake Tool
#tool nuget:?package=Orleans.SyncWork&version=0.0.1

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 following 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 is, but "Grain B" cannot start its work until "Grain A" finishes its work, you've run into a situation where the limited concurrency task scheduler can never finish the work of "Grain A".

    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 (example: PasswordVerifier).

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

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

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

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

public class PasswordVerifierResponse
{
    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, PasswordVerifierResponse>>(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.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
8.1.12 335 2/10/2024
8.0.3 346 1/18/2024
7.2.6 1,946 10/8/2023
7.1.3 4,840 1/8/2023
7.0.1 751 12/22/2022
1.5.9 4,039 1/7/2022
1.4.10 725 12/22/2021
1.3.7 688 12/14/2021
1.2.10 787 12/12/2021
1.1.9 1,365 11/29/2021
1.1.8-prerelease 1,874 11/29/2021
1.0.4 682 11/22/2021
0.0.1 1,250 11/19/2021