├── logo.png ├── global.json ├── .github ├── dependabot.yaml └── workflows │ ├── pr_validation.yaml │ └── publish_nuget.yml ├── Akka.Serialization.MessagePack ├── Internal │ ├── TypeDict.cs │ ├── TypeDictCtr.cs │ └── IntIndexedMessagePackFormatterDict.cs ├── Resolvers │ ├── SurrogateResolvable.cs │ ├── SerializableResolver.cs │ ├── BackwardsCompatibleSurrogatedFormatterResolver.cs │ ├── ExceptionAsSerializableResolver.cs │ ├── SurrogatedFormatterResolver.cs │ └── PolymorphicFormatterResolver.cs ├── Akka.Serialization.MessagePack.csproj ├── Sample.hocon ├── Formatters │ ├── PolymorphicFormatter.cs │ ├── SerializableFormatter.cs │ ├── SurrogatedFormatter.cs │ └── BackwardsCompatibleSurrogatedFormatter.cs ├── MessagePackTypeFilteringOptions.cs ├── MsgPackSerializerSettings.cs └── MsgPackSerializer.cs ├── Akka.Serialization.Testkit ├── Akka.Serialization.Testkit.csproj ├── Util │ └── ConfigFactory.cs ├── AkkaMessagesTests.cs ├── IncapsulationTests.cs ├── CustomMessagesTests.cs ├── CollectionsTests.cs ├── PrimitiveSerializerTests.cs ├── ExceptionsTests.cs ├── PolymorphismTests.cs └── ImmutableMessagesTests.cs ├── NuGet.Config ├── Akka.Serialization.MessagePack.Benchmarks ├── Akka.Serialization.MessagePack.Benchmarks.csproj ├── Program.cs └── SerializationBenchmarks.cs ├── Akka.Serialization.MessagePack.Tests ├── Akka.Serialization.MessagePack.Tests.csproj ├── MsgPackSerializersTests.cs └── MsgPackVersionToleranceTests.cs ├── scripts ├── bumpVersion.ps1 └── getReleaseNotes.ps1 ├── Directory.Packages.props ├── Directory.Build.props ├── RELEASE_NOTES.md ├── Akka.Serialization.MessagePack.sln ├── README.md ├── .gitignore └── LICENSE /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akkadotnet/Akka.Serialization.MessagePack/HEAD/logo.png -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "rollForward": "latestMinor", 4 | "version": "8.0.100" 5 | } 6 | } -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | time: "11:00" 9 | 10 | - package-ecosystem: nuget 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | time: "11:00" 15 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Internal/TypeDict.cs: -------------------------------------------------------------------------------- 1 | namespace Akka.Serialization.MessagePack.Resolvers; 2 | 3 | /// 4 | /// Used to provide indexes into a fast array lookup for resolvers. 5 | /// 6 | internal static class TypeDict 7 | { 8 | public static readonly int TypeVal = TypeDictCtr.doNotCallExternally(); 9 | } -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Internal/TypeDictCtr.cs: -------------------------------------------------------------------------------- 1 | namespace Akka.Serialization.MessagePack.Resolvers; 2 | 3 | /// 4 | /// Used as a Counter for calls. 5 | /// 6 | internal static class TypeDictCtr 7 | { 8 | private static int _typeCtr = -1; 9 | 10 | internal static int doNotCallExternally() 11 | { 12 | return Interlocked.Increment(ref _typeCtr); 13 | } 14 | } -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Resolvers/SurrogateResolvable.cs: -------------------------------------------------------------------------------- 1 | using Akka.Util; 2 | 3 | namespace Akka.Serialization.MessagePack.Resolvers 4 | { 5 | static class SurrogateResolvable 6 | { 7 | public static readonly bool IsSurrogated = (typeof(ISurrogated).IsAssignableFrom(typeof(T))); 8 | public static readonly bool IsSurrogate = 9 | typeof(ISurrogate).IsAssignableFrom(typeof(T)); 10 | } 11 | } -------------------------------------------------------------------------------- /Akka.Serialization.Testkit/Akka.Serialization.Testkit.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack.Benchmarks/Akka.Serialization.MessagePack.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | true 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BenchmarkDotNet.Configs; 3 | using BenchmarkDotNet.Environments; 4 | using BenchmarkDotNet.Jobs; 5 | using BenchmarkDotNet.Running; 6 | using BenchmarkDotNet.Toolchains.CsProj; 7 | 8 | namespace Akka.Serialization.MessagePack.Benchmarks 9 | { 10 | public class MyConfig : ManualConfig 11 | { 12 | public MyConfig() 13 | { 14 | Add(Job.Default.With(CsProjCoreToolchain.NetCoreApp31).With(Platform.X64).WithGcServer(true)); 15 | this.Options = 16 | Options | ConfigOptions.DisableOptimizationsValidator; 17 | } 18 | } 19 | 20 | class Program 21 | { 22 | static void Main(string[] args) 23 | { 24 | BenchmarkRunner.Run(); 25 | 26 | Console.ReadLine(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Akka.Serialization.MessagePack.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Akka.Serialization.MessagePack 5 | MessagePack serializer for Akka.NET 6 | netstandard2.0;net6.0 7 | Akka.Serialization.MessagePack2 8 | true 9 | $(NoWarn);CS1591 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack.Tests/Akka.Serialization.MessagePack.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/pr_validation.yaml: -------------------------------------------------------------------------------- 1 | name: pr_validation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | - main 9 | pull_request: 10 | branches: 11 | - master 12 | - dev 13 | - main 14 | 15 | jobs: 16 | test: 17 | name: Test-${{matrix.os}} 18 | runs-on: ${{matrix.os}} 19 | 20 | strategy: 21 | matrix: 22 | os: [ubuntu-latest, windows-latest] 23 | 24 | steps: 25 | - name: "Checkout" 26 | uses: actions/checkout@v4.1.1 27 | with: 28 | lfs: true 29 | fetch-depth: 0 30 | 31 | - name: "Install .NET SDK" 32 | uses: actions/setup-dotnet@v4.0.0 33 | with: 34 | global-json-file: "./global.json" 35 | 36 | - name: "Update release notes" 37 | shell: pwsh 38 | run: | 39 | ./build.ps1 40 | 41 | - name: "dotnet build" 42 | run: dotnet build -c Release 43 | 44 | - name: "dotnet test" 45 | run: dotnet test -c Release 46 | 47 | - name: "dotnet pack" 48 | run: dotnet pack -c Release -------------------------------------------------------------------------------- /Akka.Serialization.Testkit/Util/ConfigFactory.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | 9 | namespace Akka.Serialization.Testkit.Util 10 | { 11 | public class ConfigFactory 12 | { 13 | public static string GetConfig(Type serializerType) 14 | { 15 | return @" 16 | akka.actor { 17 | serializers.msgpack = """ + serializerType.AssemblyQualifiedName + @""" 18 | serialization-bindings { 19 | ""System.Object"" = msgpack 20 | } 21 | serialization-settings { 22 | msgpack { 23 | enable-lz4-compression = false 24 | } 25 | } 26 | }"; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /scripts/bumpVersion.ps1: -------------------------------------------------------------------------------- 1 | function UpdateVersionAndReleaseNotes { 2 | param ( 3 | [Parameter(Mandatory=$true)] 4 | [PSCustomObject]$ReleaseNotesResult, 5 | 6 | [Parameter(Mandatory=$true)] 7 | [string]$XmlFilePath 8 | ) 9 | 10 | # Load XML 11 | $xmlContent = New-Object XML 12 | $xmlContent.Load($XmlFilePath) 13 | 14 | # Update VersionPrefix and PackageReleaseNotes 15 | $versionPrefixElement = $xmlContent.SelectSingleNode("//VersionPrefix") 16 | $versionPrefixElement.InnerText = $ReleaseNotesResult.Version 17 | 18 | $packageReleaseNotesElement = $xmlContent.SelectSingleNode("//PackageReleaseNotes") 19 | $packageReleaseNotesElement.InnerText = $ReleaseNotesResult.ReleaseNotes 20 | 21 | # Save the updated XML 22 | $xmlContent.Save($XmlFilePath) 23 | } 24 | 25 | # Usage example: 26 | # $notes = Get-ReleaseNotes -MarkdownFile "$PSScriptRoot\RELEASE_NOTES.md" 27 | # $propsPath = Join-Path -Path (Get-Item $PSScriptRoot).Parent.FullName -ChildPath "Directory.Build.props" 28 | # UpdateVersionAndReleaseNotes -ReleaseNotesResult $notes -XmlFilePath $propsPath 29 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Sample.hocon: -------------------------------------------------------------------------------- 1 | 2 | akka.actor { 3 | serializers.messagepack = "Akka.Serialization.MessagePack.MsgPackSerializer, Akka.Serialization.MessagePack" 4 | serialization-bindings { 5 | "System.Object" = messagepack 6 | } 7 | serialization-settings { 8 | messagepack { 9 | 10 | # Use "Lz4BlockArray" or "Lz4Block" to enable. 11 | # "Lz4BlockArray" is reccomended by MessagePack author. 12 | # Note that all sides must have this set! 13 | enable-lz4-compression = "none" 14 | 15 | # If False, Assembly version mismatches on typeless types may throw! 16 | allow-assembly-version-mismatch = true 17 | 18 | # If True, Assembly version is included in typeless serialized data. 19 | omit-assembly-version = true 20 | 21 | # A set of converters with FQCN (Including assembly) to use, 22 | # before falling back to the TypelessFormatter. 23 | # Note that Other converters like Akka surrogates are registered -before- 24 | # these. If you need to party on those types, use 'converters-override' instead 25 | converters = [] 26 | 27 | # A set of 'Override' converters with FQCN (including assmebly) to use, 28 | # BEFORE All other converters. 29 | # This may break Akka Serialization if you are not careful! 30 | converters-override = [] 31 | 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | Copyright © 2013-2024 Akka.NET Project 4 | $(NoWarn);CS1591;NU1701;CA1707; 5 | 1.1.0 6 | 7 | Akka.NET Team 8 | https://github.com/akkadotnet/Akka.Serialization.MessagePack 9 | * [Resolved `AK2001`: `if` statements not cleanly managed by Code Fix](https://github.com/akkadotnet/akka.analyzers/pull/46) 10 | akka;actors;actor model;Akka;concurrency;messagepack;serializer;serialization 11 | README.md 12 | Apache-2.0 13 | logo.png 14 | 15 | MsgPack serialization support for Akka.NET. 16 | 17 | 18 | 19 | 20 | latest 21 | enable 22 | enable 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /scripts/getReleaseNotes.ps1: -------------------------------------------------------------------------------- 1 | function Get-ReleaseNotes { 2 | param ( 3 | [Parameter(Mandatory=$true)] 4 | [string]$MarkdownFile 5 | ) 6 | 7 | # Read markdown file content 8 | $content = Get-Content -Path $MarkdownFile -Raw 9 | 10 | # Split content based on headers 11 | $sections = $content -split "####" 12 | 13 | # Output object to store result 14 | $outputObject = [PSCustomObject]@{ 15 | Version = $null 16 | Date = $null 17 | ReleaseNotes = $null 18 | } 19 | 20 | # Check if we have at least 3 sections (1. Before the header, 2. Header, 3. Release notes) 21 | if ($sections.Count -ge 3) { 22 | $header = $sections[1].Trim() 23 | $releaseNotes = $sections[2].Trim() 24 | 25 | # Extract version and date from the header 26 | $headerParts = $header -split " ", 2 27 | if ($headerParts.Count -eq 2) { 28 | $outputObject.Version = $headerParts[0] 29 | $outputObject.Date = $headerParts[1] 30 | } 31 | 32 | $outputObject.ReleaseNotes = $releaseNotes 33 | } 34 | 35 | # Return the output object 36 | return $outputObject 37 | } 38 | 39 | # Call function example: 40 | #$result = Get-ReleaseNotes -MarkdownFile "$PSScriptRoot\RELEASE_NOTES.md" 41 | #Write-Output "Version: $($result.Version)" 42 | #Write-Output "Date: $($result.Date)" 43 | #Write-Output "Release Notes:" 44 | #Write-Output $result.ReleaseNotes 45 | -------------------------------------------------------------------------------- /.github/workflows/publish_nuget.yml: -------------------------------------------------------------------------------- 1 | name: Publish NuGet 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish-nuget: 10 | 11 | name: publish-nuget 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Setup .NET Core 20 | uses: actions/setup-dotnet@v4 21 | with: 22 | dotnet-version: ${{ env.DOTNET_VERSION }} 23 | 24 | - name: "Update release notes" 25 | shell: pwsh 26 | run: | 27 | ./build.ps1 28 | 29 | - name: Create Packages 30 | run: dotnet pack /p:PackageVersion=${{ github.ref_name }} -c Release -o ./output 31 | 32 | - name: Push Packages 33 | run: dotnet nuget push "output/*.nupkg" -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json 34 | 35 | - name: release 36 | uses: actions/create-release@v1 37 | id: create_release 38 | with: 39 | draft: false 40 | prerelease: false 41 | release_name: 'Akka.Serialization.MessagePack ${{ github.ref_name }}' 42 | tag_name: ${{ github.ref }} 43 | body_path: RELEASE_NOTES.md 44 | env: 45 | GITHUB_TOKEN: ${{ github.token }} 46 | 47 | - name: Upload Release Asset 48 | uses: AButler/upload-release-assets@v3.0 49 | with: 50 | repo-token: ${{ github.token }} 51 | release-tag: ${{ github.ref_name }} 52 | files: 'output/*.nupkg' 53 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Resolvers/SerializableResolver.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | using System; 7 | using MessagePack; 8 | using MessagePack.Formatters; 9 | 10 | namespace Akka.Serialization.MessagePack.Resolvers 11 | { 12 | public class SerializableResolver : IFormatterResolver 13 | { 14 | public static readonly IFormatterResolver Instance = new SerializableResolver(); 15 | SerializableResolver() { } 16 | 17 | public IMessagePackFormatter GetFormatter() => FormatterCache.Formatter; 18 | 19 | static class FormatterCache 20 | { 21 | public static readonly IMessagePackFormatter Formatter; 22 | static FormatterCache() => Formatter = (IMessagePackFormatter)SerializableFormatterHelper.GetFormatter(); 23 | } 24 | } 25 | 26 | internal static class SerializableFormatterHelper 27 | { 28 | internal static object GetFormatter() 29 | { 30 | return SerializableFormatterAssignable.IsAssignableFrom 31 | ? new SerializableFormatter() 32 | : null; 33 | } 34 | } 35 | 36 | internal static class SerializableFormatterAssignable 37 | { 38 | public static readonly bool IsAssignableFrom = 39 | typeof(Exception).IsAssignableFrom(typeof(T)); 40 | } 41 | } -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | #### 1.5.31 November 26th 2024 #### 2 | 3 | This is the RTM release of Akka.Serialization.MessagePack 4 | 5 | * [Upgrade Akka.Net to 1.5.31](https://github.com/akkadotnet/akka.net/releases/tag/1.5.31) 6 | * [Bump MessagePack to 2.5.192](https://github.com/akkadotnet/Akka.Serialization.MessagePack/pull/80) 7 | * [Bump CommunityToolkit.HighPerformance to 8.3.2](https://github.com/akkadotnet/Akka.Serialization.MessagePack/pull/68) 8 | 9 | #### 1.5.31-beta1 November 18th 2024 #### 10 | 11 | * [Upgrade Akka.Net to 1.5.31](https://github.com/akkadotnet/akka.net/releases/tag/1.5.31) 12 | * [Bump MessagePack to 2.5.192](https://github.com/akkadotnet/Akka.Serialization.MessagePack/pull/80) 13 | * [Bump CommunityToolkit.HighPerformance to 8.3.2](https://github.com/akkadotnet/Akka.Serialization.MessagePack/pull/68) 14 | 15 | #### 1.5.16-beta1 February 14 2024 #### 16 | 17 | Akka.Serialization.MessagePack Beta release for Akka.NET v1.5 18 | 19 | * [Upgrade Akka.Net to 1.5.16](https://github.com/akkadotnet/akka.net/releases/tag/1.5.16) 20 | * [Better polymorphism handling](https://github.com/akkadotnet/Akka.Serialization.MessagePack/pull/27) 21 | * [Use int array lookup for polymorphic resolver](https://github.com/akkadotnet/Akka.Serialization.MessagePack/pull/42) 22 | * [Handle edge case for dictionary lookup](https://github.com/akkadotnet/Akka.Serialization.MessagePack/pull/44) 23 | * [Fix off-by-one waste in IntIndexedDict](https://github.com/akkadotnet/Akka.Serialization.MessagePack/pull/45) 24 | * [Bump MessagePack to 2.4](https://github.com/akkadotnet/Akka.Serialization.MessagePack/pull/24) 25 | * [Bump CommunityToolkit.HighPerformance to 8.2.2](https://github.com/akkadotnet/Akka.Serialization.MessagePack/pull/36) 26 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Resolvers/BackwardsCompatibleSurrogatedFormatterResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using Akka.Actor; 4 | using MessagePack; 5 | using MessagePack.Formatters; 6 | 7 | namespace Akka.Serialization.MessagePack.Resolvers 8 | { 9 | public class BackwardsCompatibleSurrogatedFormatterResolver : IFormatterResolver, IDoNotUsePolymorphicFormatter 10 | { 11 | 12 | private readonly ConcurrentDictionary 13 | _formatterCache = 14 | new ConcurrentDictionary(); 15 | 16 | private readonly Func _formatterCreateFunc; 17 | public BackwardsCompatibleSurrogatedFormatterResolver(ExtendedActorSystem system) 18 | { 19 | //Cast in the func since we'll have to cache anyway. 20 | //The alternative is making a 'nullable' func in another static class, 21 | //But that may result in too much garbage for other types. 22 | _formatterCreateFunc = t => 23 | (IMessagePackFormatter)typeof(BackwardsCompatibleSurrogatedFormatter<>) 24 | .MakeGenericType(t) 25 | .GetConstructor(new[] { typeof(ActorSystem) }) 26 | .Invoke(new[] { system }); 27 | 28 | } 29 | public IMessagePackFormatter GetFormatter() 30 | { 31 | if (SurrogateResolvable.IsSurrogated) 32 | { 33 | return (IMessagePackFormatter)_formatterCache.GetOrAdd( 34 | typeof(T), 35 | _formatterCreateFunc); 36 | } 37 | else 38 | { 39 | return null; 40 | } 41 | } 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Resolvers/ExceptionAsSerializableResolver.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | #if SERIALIZATION 7 | using System; 8 | using MessagePack; 9 | using MessagePack.Formatters; 10 | 11 | namespace Akka.Serialization.MessagePack.Resolvers 12 | { 13 | public class ExceptionAsSerializableResolver : IFormatterResolver 14 | { 15 | public static readonly IFormatterResolver Instance = new ExceptionAsSerializableResolver(); 16 | ExceptionAsSerializableResolver() { } 17 | 18 | public IMessagePackFormatter GetFormatter() => FormatterCache.Formatter; 19 | 20 | static class FormatterCache 21 | { 22 | public static readonly IMessagePackFormatter Formatter; 23 | static FormatterCache() => Formatter = (IMessagePackFormatter)ExceptionAsSerializableFormatterHelper.GetFormatter(); 24 | } 25 | } 26 | 27 | internal static class ExceptionAsSerializableFormatterHelper 28 | { 29 | internal static object GetFormatter() 30 | { 31 | return ExceptionAsSerializableFormatterAssignable 32 | .FormatterOrNull; 33 | } 34 | } 35 | 36 | /// 37 | /// A Generic-type cache to 38 | /// 39 | /// 40 | internal static class ExceptionAsSerializableFormatterAssignable 41 | { 42 | public static readonly object FormatterOrNull = 43 | typeof(Exception).IsAssignableFrom(typeof(T)) 44 | ? new SerializableFormatter() 45 | : null; 46 | public static readonly bool IsAssignableFrom = 47 | FormatterOrNull != null; 48 | } 49 | } 50 | #endif -------------------------------------------------------------------------------- /Akka.Serialization.Testkit/AkkaMessagesTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Akka.Actor; 9 | using Akka.Serialization.Testkit.Util; 10 | using Akka.TestKit.TestActors; 11 | using Xunit; 12 | 13 | namespace Akka.Serialization.Testkit 14 | { 15 | public abstract class AkkaMessagesTests : TestKit.Xunit2.TestKit 16 | { 17 | protected AkkaMessagesTests(Type serializerType) : base(ConfigFactory.GetConfig(serializerType)) 18 | { 19 | } 20 | 21 | [Fact] 22 | public void Can_serialize_ActorRef() 23 | { 24 | var actorRef = ActorOf(); 25 | AssertEqual(actorRef); 26 | } 27 | 28 | [Fact] 29 | public void Can_serialize_RootActorPath() 30 | { 31 | var uri = "akka.tcp://sys@localhost:9000"; 32 | var actorPath = ActorPath.Parse(uri); 33 | AssertEqual(actorPath); 34 | } 35 | 36 | [Fact] 37 | public void Can_serialize_ActorPath() 38 | { 39 | var uri = "akka.tcp://sys@localhost:9000/user/actor"; 40 | var actorPath = ActorPath.Parse(uri); 41 | AssertEqual(actorPath); 42 | } 43 | 44 | protected T AssertAndReturn(T message) 45 | { 46 | var serializer = Sys.Serialization.FindSerializerFor(message); 47 | var serialized = serializer.ToBinary(message); 48 | var result = serializer.FromBinary(serialized, typeof(T)); 49 | return (T)result; 50 | } 51 | 52 | protected void AssertEqual(T message) 53 | { 54 | var deserialized = AssertAndReturn(message); 55 | Assert.Equal(message, deserialized); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Resolvers/SurrogatedFormatterResolver.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Collections.Concurrent; 9 | using System.Collections.Generic; 10 | using System.Runtime.CompilerServices; 11 | using Akka.Actor; 12 | using Akka.Util.Internal; 13 | using MessagePack; 14 | using MessagePack.Formatters; 15 | 16 | namespace Akka.Serialization.MessagePack.Resolvers 17 | { 18 | public class SurrogatedFormatterResolver : IFormatterResolver, IDoNotUsePolymorphicFormatter 19 | { 20 | 21 | private readonly ConcurrentDictionary 22 | _formatterCache = 23 | new ConcurrentDictionary(); 24 | 25 | private readonly Func _formatterCreateFunc; 26 | public SurrogatedFormatterResolver(ExtendedActorSystem system) 27 | { 28 | //Cast in the func since we'll have to cache anyway. 29 | //The alternative is making a 'nullable' func in another static class, 30 | //But that may result in too much garbage for other types. 31 | _formatterCreateFunc = t => 32 | (IMessagePackFormatter)typeof(SurrogatedFormatter<>) 33 | .MakeGenericType(t) 34 | .GetConstructor(new[] { typeof(ActorSystem) }) 35 | .Invoke(new[] { system }); 36 | 37 | } 38 | public IMessagePackFormatter GetFormatter() 39 | { 40 | if (SurrogateResolvable.IsSurrogated) 41 | { 42 | if (_formatterCache.TryGetValue(typeof(T),out var formatter)) 43 | { 44 | return (IMessagePackFormatter)formatter; 45 | } 46 | else 47 | { 48 | return (IMessagePackFormatter)_formatterCache.GetOrAdd(typeof(T), 49 | _formatterCreateFunc); 50 | } 51 | } 52 | else 53 | { 54 | return null!; 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack.Tests/MsgPackSerializersTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using Akka.Serialization.Testkit; 8 | using Xunit; 9 | 10 | namespace Akka.Serialization.MessagePack.Tests 11 | { 12 | public class MsgPackAkkaMessagesTests : AkkaMessagesTests 13 | { 14 | public MsgPackAkkaMessagesTests() : base(typeof(MsgPackSerializer)) 15 | { 16 | } 17 | } 18 | 19 | public class MsgPackCollectionsTests : CollectionsTests 20 | { 21 | public MsgPackCollectionsTests() : base(typeof(MsgPackSerializer)) 22 | { 23 | } 24 | } 25 | 26 | public class MsgPackCustomMessagesTests : CustomMessagesTests 27 | { 28 | public MsgPackCustomMessagesTests() : base(typeof(MsgPackSerializer)) 29 | { 30 | } 31 | } 32 | 33 | public class MsgPackExceptionsTests : ExceptionsTests 34 | { 35 | public MsgPackExceptionsTests() : base(typeof(MsgPackSerializer)) 36 | { 37 | } 38 | } 39 | 40 | public class MsgPackImmutableMessagesTests : ImmutableMessagesTests 41 | { 42 | public MsgPackImmutableMessagesTests() : base(typeof(MsgPackSerializer)) 43 | { 44 | } 45 | } 46 | 47 | public class MsgPackPolymorphismTests : PolymorphismTests 48 | { 49 | public MsgPackPolymorphismTests() : base(typeof(MsgPackSerializer)) 50 | { 51 | } 52 | } 53 | 54 | public class MsgPackIncapsulationTests : IncapsulationTests 55 | { 56 | public MsgPackIncapsulationTests() : base(typeof(MsgPackSerializer)) 57 | { 58 | } 59 | 60 | //[Fact(Skip = "Not supported yet")] 61 | //public override void Can_Serialize_a_class_with_internal_constructor() 62 | //{ 63 | //} 64 | 65 | //[Fact(Skip = "Not supported yet")] 66 | //public override void Can_Serialize_a_class_with_private_constructor() 67 | //{ 68 | //} 69 | 70 | //[Fact(Skip = "Not supported yet")] 71 | //public override void Can_Serialize_internal_class() 72 | //{ 73 | //} 74 | } 75 | 76 | public class MsgPackPrimiviteSerializerTests : PrimitiveSerializerTests 77 | { 78 | public MsgPackPrimiviteSerializerTests() : base(typeof(MsgPackSerializer)) 79 | { 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Formatters/PolymorphicFormatter.cs: -------------------------------------------------------------------------------- 1 | using MessagePack; 2 | using MessagePack.Formatters; 3 | 4 | namespace Akka.Serialization.MessagePack.Resolvers 5 | { 6 | public class PolymorphicFormatter : IMessagePackFormatter 7 | { 8 | //private static readonly ForceTypelessFormatter _typelessFormatter = 9 | // new ForceTypelessFormatter(); 10 | 11 | private readonly IMessagePackFormatter _normalFormatter; 12 | 13 | public PolymorphicFormatter(IMessagePackFormatter normalFormatter) 14 | { 15 | _normalFormatter = normalFormatter; 16 | } 17 | public void Serialize(ref MessagePackWriter writer, T value, 18 | MessagePackSerializerOptions options) 19 | { 20 | if (value == null) 21 | { 22 | writer.WriteNil(); 23 | } 24 | else if (value.GetType() == typeof(T)) 25 | { 26 | _normalFormatter.Serialize(ref writer, value, options); 27 | } 28 | else 29 | { 30 | TypelessFormatter.Instance.Serialize(ref writer, value, 31 | options); 32 | } 33 | } 34 | 35 | public T Deserialize(ref MessagePackReader reader, 36 | MessagePackSerializerOptions options) 37 | { 38 | // It's a trap! 39 | // TryReadExtensionFormatHeader will throw if Code is not Ext!!! 40 | // So we -must- first peek at NextCode and make sure it is one of 41 | // The correct types for Ext Data. 42 | switch (reader.NextCode) 43 | { 44 | case MessagePackCode.Ext8: 45 | case MessagePackCode.Ext16: 46 | case MessagePackCode.Ext32: 47 | case MessagePackCode.FixExt1: 48 | case MessagePackCode.FixExt2: 49 | case MessagePackCode.FixExt4: 50 | case MessagePackCode.FixExt8: 51 | case MessagePackCode.FixExt16: 52 | { 53 | if (reader.CreatePeekReader().TryReadExtensionFormatHeader(out var header) && 54 | header.TypeCode == 100) 55 | return (T)TypelessFormatter.Instance.Deserialize(ref reader, 56 | options); 57 | break; 58 | } 59 | case MessagePackCode.Nil: 60 | { 61 | reader.ReadNil(); 62 | return default!; 63 | } 64 | } 65 | 66 | return _normalFormatter.Deserialize(ref reader, options); 67 | } 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Formatters/SerializableFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.Serialization; 3 | using System.Text; 4 | using Akka.Util.Internal; 5 | using MessagePack; 6 | using MessagePack.Formatters; 7 | 8 | namespace Akka.Serialization.MessagePack.Resolvers 9 | { 10 | public class SerializableFormatter : IMessagePackFormatter 11 | { 12 | private static readonly IMessagePackFormatter ObjectFormatter = TypelessFormatter.Instance; 13 | 14 | public void Serialize(ref MessagePackWriter writer, T value, 15 | MessagePackSerializerOptions options) 16 | { 17 | if (value == null) 18 | { 19 | writer.WriteNil(); 20 | return; 21 | } 22 | 23 | var serializable = value as ISerializable; 24 | var serializationInfo = new SerializationInfo(value.GetType(), new FormatterConverter()); 25 | serializable.GetObjectData(serializationInfo, new StreamingContext()); 26 | 27 | writer.WriteMapHeader(serializationInfo.MemberCount); 28 | foreach (var info in serializationInfo) 29 | { 30 | writer.WriteString(Encoding.UTF8.GetBytes(info.Name)); 31 | ObjectFormatter.Serialize(ref writer, info.Value, options); 32 | } 33 | } 34 | 35 | public T Deserialize(ref MessagePackReader reader, 36 | MessagePackSerializerOptions options) 37 | { 38 | if (reader.IsNil) 39 | { 40 | reader.ReadNil(); 41 | return default(T); 42 | } 43 | 44 | var serializationInfo = new SerializationInfo(typeof(T), new FormatterConverter()); 45 | 46 | var len = reader.ReadMapHeader(); 47 | for (int i = 0; i < len; i++) 48 | { 49 | var key = reader.ReadString(); 50 | var val = ObjectFormatter.Deserialize(ref reader,options); 51 | serializationInfo.AddValue(key, val); 52 | } 53 | 54 | ISerializable obj = null; 55 | ConstructorInfo constructorInfo = typeof(T).GetConstructor( 56 | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, 57 | null, 58 | new[] { typeof(SerializationInfo), typeof(StreamingContext) }, 59 | null); 60 | 61 | if (constructorInfo != null) 62 | { 63 | object[] args = { serializationInfo, new StreamingContext() }; 64 | obj = constructorInfo.Invoke(args).AsInstanceOf(); 65 | } 66 | return (T)obj; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.10 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Serialization.MessagePack", "Akka.Serialization.MessagePack\Akka.Serialization.MessagePack.csproj", "{0EA6EE13-61B2-4CBD-9241-C19ECA04C015}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Serialization.MessagePack.Tests", "Akka.Serialization.MessagePack.Tests\Akka.Serialization.MessagePack.Tests.csproj", "{CFDA0472-80EE-42E9-8629-B443C172360E}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Serialization.Testkit", "Akka.Serialization.Testkit\Akka.Serialization.Testkit.csproj", "{F1DBA496-B62F-436B-879A-481595B74102}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{613896F1-E616-40AB-B7FF-5566285A6595}" 13 | ProjectSection(SolutionItems) = preProject 14 | README.md = README.md 15 | RELEASE_NOTES.md = RELEASE_NOTES.md 16 | Directory.Build.props = Directory.Build.props 17 | Directory.Packages.props = Directory.Packages.props 18 | build.ps1 = build.ps1 19 | EndProjectSection 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Serialization.MessagePack.Benchmarks", "Akka.Serialization.MessagePack.Benchmarks\Akka.Serialization.MessagePack.Benchmarks.csproj", "{A132C115-CCD5-45F0-9D6C-EBFEFF8CB208}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Release|Any CPU = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {0EA6EE13-61B2-4CBD-9241-C19ECA04C015}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {0EA6EE13-61B2-4CBD-9241-C19ECA04C015}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {0EA6EE13-61B2-4CBD-9241-C19ECA04C015}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {0EA6EE13-61B2-4CBD-9241-C19ECA04C015}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {CFDA0472-80EE-42E9-8629-B443C172360E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {CFDA0472-80EE-42E9-8629-B443C172360E}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {CFDA0472-80EE-42E9-8629-B443C172360E}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {CFDA0472-80EE-42E9-8629-B443C172360E}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {F1DBA496-B62F-436B-879A-481595B74102}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {F1DBA496-B62F-436B-879A-481595B74102}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {F1DBA496-B62F-436B-879A-481595B74102}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {F1DBA496-B62F-436B-879A-481595B74102}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {A132C115-CCD5-45F0-9D6C-EBFEFF8CB208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {A132C115-CCD5-45F0-9D6C-EBFEFF8CB208}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {A132C115-CCD5-45F0-9D6C-EBFEFF8CB208}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {A132C115-CCD5-45F0-9D6C-EBFEFF8CB208}.Release|Any CPU.Build.0 = Release|Any CPU 45 | EndGlobalSection 46 | GlobalSection(SolutionProperties) = preSolution 47 | HideSolutionNode = FALSE 48 | EndGlobalSection 49 | GlobalSection(ExtensibilityGlobals) = postSolution 50 | SolutionGuid = {9946F5AB-875A-4642-8890-2D25325D163B} 51 | EndGlobalSection 52 | EndGlobal 53 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Resolvers/PolymorphicFormatterResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Runtime.CompilerServices; 4 | using Akka.Util; 5 | using CommunityToolkit.HighPerformance; 6 | using MessagePack; 7 | using MessagePack.Formatters; 8 | 9 | namespace Akka.Serialization.MessagePack.Resolvers 10 | { 11 | /// 12 | /// A 'Wrapping' resolver that, 13 | /// When provided as the 'base' resolver for MessagePack, 14 | /// Applies Polymorphic formatting rules to serialization, 15 | /// When the instanced type does not match runtime type. 16 | /// If you do not want this behavior, you may explicitly opt out 17 | /// Via ISurrogate, or have your in-chain Resolver provide a formatter 18 | /// That has the 19 | /// Interface inherited 20 | public class PolymorphicFormatterResolver : IFormatterResolver 21 | { 22 | private readonly IFormatterResolver _normalResolver; 23 | 24 | private readonly IntIndexedMessagePackFormatterDict _formatterDict = 25 | new IntIndexedMessagePackFormatterDict(256); 26 | 27 | public PolymorphicFormatterResolver(IFormatterResolver normalResolver) 28 | { 29 | _normalResolver = normalResolver; 30 | } 31 | 32 | public IMessagePackFormatter GetFormatter() 33 | { 34 | if (_formatterDict.TryGet(TypeDict.TypeVal,out var formatter)) 35 | { 36 | //IMPORTANT! 37 | // This assumes that MakeValue is doing a proper 38 | // sanity check on the formatter 39 | // produced by underlying resolver. 40 | return Unsafe.As>(formatter); 41 | } 42 | else 43 | { 44 | return MakeValue(); 45 | } 46 | } 47 | 48 | [MethodImpl(MethodImplOptions.NoInlining)] 49 | private IMessagePackFormatter MakeValue() 50 | { 51 | // IMPORTANT!!! 52 | // For type safety make sure to read comments!!! 53 | // If you break below assumptions, 54 | // GetFormatter may require refactor! 55 | // 56 | // The explicit-casting here, 57 | // is to ensure we do a type check on returned value. 58 | // This helps guarantee our Unsafe calls are safe in context. 59 | // Since this is not at all the hot path, 60 | // easy cost to justify for retaining type safety. 61 | // 62 | // Minor Note: 63 | // It's possible that we wastefully alloc here, 64 | // However our 'wrapped' formatter should be caching, 65 | // and the Polymorphic formatter itself is just a ref, 66 | // so it's a cheap temporary cost to pay. 67 | IMessagePackFormatter formatterToUse = 68 | (IMessagePackFormatter)(object)_normalResolver 69 | .GetFormatter(); 70 | if ((formatterToUse is IDoNotUsePolymorphicFormatter) == false && 71 | typeof(T).IsClass) 72 | { 73 | if ((typeof(T).IsAbstract || !typeof(T).IsSealed) && 74 | typeof(ISurrogated).IsAssignableFrom(typeof(T))== false) 75 | { 76 | formatterToUse = 77 | new PolymorphicFormatter(formatterToUse); 78 | } 79 | } 80 | 81 | return (IMessagePackFormatter)_formatterDict.TryAdd(TypeDict.TypeVal,formatterToUse); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/MessagePackTypeFilteringOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using MessagePack; 7 | using MessagePack.Formatters; 8 | 9 | namespace Akka.Serialization.MessagePack 10 | { 11 | /// 12 | /// A Custom 13 | /// That allows us to, among other things, 14 | /// Apply custom type filtering logic. 15 | /// 16 | public class MessagePackTypeFilteringOptions : MessagePackSerializerOptions 17 | { 18 | private MessagePackTypeFilteringOptions(IFormatterResolver resolver) : base(resolver) 19 | { 20 | } 21 | 22 | public MessagePackTypeFilteringOptions(MessagePackSerializerOptions copyFrom) : base(copyFrom) 23 | { 24 | } 25 | 26 | private static ReadOnlyCollection unsafeTypesDenySet = 27 | new ReadOnlyCollection(new[] 28 | { 29 | "System.Security.Claims.ClaimsIdentity", 30 | "System.Windows.Forms.AxHost.State", 31 | "System.Windows.Data.ObjectDataProvider", 32 | "System.Management.Automation.PSObject", 33 | "System.Web.Security.RolePrincipal", 34 | "System.IdentityModel.Tokens.SessionSecurityToken", 35 | "SessionViewStateHistoryItem", 36 | "TextFormattingRunProperties", 37 | "ToolboxItemContainer", 38 | "System.Security.Principal.WindowsClaimsIdentity", 39 | "System.Security.Principal.WindowsIdentity", 40 | "System.Security.Principal.WindowsPrincipal", 41 | "System.CodeDom.Compiler.TempFileCollection", 42 | "System.IO.FileSystemInfo", 43 | "System.Activities.Presentation.WorkflowDesigner", 44 | "System.Windows.ResourceDictionary", 45 | "System.Windows.Forms.BindingSource", 46 | "Microsoft.Exchange.Management.SystemManager.WinForms.ExchangeSettingsProvider", 47 | "System.Diagnostics.Process", 48 | "System.Management.IWbemClassObjectFreeThreaded" 49 | }); 50 | 51 | public static bool UnsafeInheritanceCheck(Type type) 52 | { 53 | if (type.IsValueType) 54 | return false; 55 | var currentBase = type.BaseType; 56 | while (currentBase != null) 57 | { 58 | if (unsafeTypesDenySet.Any(r => currentBase.FullName?.Contains(r) ?? false)) 59 | return true; 60 | currentBase = currentBase.BaseType; 61 | } 62 | 63 | return false; 64 | } 65 | 66 | private readonly ConcurrentDictionary allowedCache = 67 | new ConcurrentDictionary(); 68 | public override void ThrowIfDeserializingTypeIsDisallowed(Type type) 69 | { 70 | if (!allowedCache.TryGetValue(type, out var allowed)) 71 | { 72 | allowed = setAllowed(type); 73 | } 74 | if (!allowed) 75 | { 76 | throw new MessagePackSerializationException("Deserialization attempted to create the type " + type.FullName + " which is not allowed."); 77 | } 78 | //base.ThrowIfDeserializingTypeIsDisallowed(type); 79 | } 80 | 81 | [MethodImpl(MethodImplOptions.NoInlining)] 82 | private bool setAllowed(Type type) 83 | { 84 | bool allowed; 85 | allowed = 86 | unsafeTypesDenySet.Any(r => type.FullName.Contains(r)) == 87 | false && UnsafeInheritanceCheck(type) == false; 88 | allowedCache[type] = allowed; 89 | return allowed; 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Internal/IntIndexedMessagePackFormatterDict.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using MessagePack.Formatters; 3 | 4 | namespace Akka.Serialization.MessagePack.Resolvers; 5 | 6 | /// 7 | /// A fast, Grow-only structure for holding Created formatters. 8 | /// Uses Int indexes per-type to avoid ConcurrentDictionary bucket costs, 9 | /// And avoids volatile/lock entirely on the happy path. 10 | /// 11 | /// 12 | /// This can be (in some cases) less space efficient than a normal dictionary, 13 | /// However, for most internal uses, the cost is going to be fairly bounded, 14 | /// and 's behavior results in 15 | /// being a hot path, 16 | /// So in general the alloc cost is worth it. 17 | /// 18 | internal sealed class IntIndexedMessagePackFormatterDict 19 | { 20 | private IMessagePackFormatter?[] _formatters; 21 | private readonly object _lockObj = new(); 22 | public IntIndexedMessagePackFormatterDict(int initialSize) 23 | { 24 | _formatters = new IMessagePackFormatter[256]; 25 | } 26 | 27 | public bool TryGet(int i, out IMessagePackFormatter? formatter) 28 | { 29 | //No Fence here. 30 | //If the read happened to be dirty, that's fine. 31 | //Should it happen, it just means we trust `TryAdd` 32 | //which does a full fence and then checks state. 33 | //Shouldn't happen much and only on startup... 34 | var f = _formatters; 35 | if (i > f.Length) 36 | { 37 | formatter = default; 38 | return false; 39 | } 40 | else 41 | { 42 | formatter = f[i]; 43 | return formatter != default; 44 | } 45 | 46 | } 47 | 48 | public IMessagePackFormatter TryAdd(int i, IMessagePackFormatter formatter) 49 | { 50 | //This may result in locks earlier on in an app, 51 | //However this will not impact types that can already be deserialized, 52 | //and since we are always in a full fence here can ensure we don't 53 | //accidentally fail to share an instance. 54 | if (i >= 0) 55 | { 56 | lock (_lockObj) 57 | { 58 | //We care about two things in the lock. 59 | //First, if we need a resize, we do the resize, 60 | // and just copy the instance over because we have lock. 61 | //Also, Guard for edge case where we get a type that misses bounds, 62 | // i.e. concurrent systems in same process. 63 | //Otherwise, we check whether we are there 64 | // (if a competitor added it on resize, we see via fence) 65 | // and if not, add ours. 66 | while (_formatters.Length <= i) 67 | { 68 | //Resize. Copy everything over first, 69 | //And then add our version before setting the new version. 70 | var newF = 71 | new IMessagePackFormatter?[_formatters.Length * 2]; 72 | _formatters.CopyTo(newF.AsSpan()); 73 | _formatters = newF; 74 | } 75 | 76 | if (_formatters[i] == null) 77 | { 78 | _formatters[i] = formatter; 79 | } 80 | } 81 | 82 | return _formatters[i]!; 83 | } 84 | else 85 | { 86 | return ThrowOutOfRangeHelper(i, formatter); 87 | } 88 | } 89 | 90 | [MethodImplAttribute(MethodImplOptions.NoInlining)] 91 | private static IMessagePackFormatter ThrowOutOfRangeHelper(int index, 92 | IMessagePackFormatter formatter) 93 | { 94 | throw new IndexOutOfRangeException( 95 | $"Value must be non-negative! Value was {index}, For Formatter {formatter?.GetType().FullName ?? "null"}"); 96 | } 97 | } -------------------------------------------------------------------------------- /Akka.Serialization.Testkit/IncapsulationTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using Akka.Serialization.Testkit.Util; 9 | using Xunit; 10 | 11 | namespace Akka.Serialization.Testkit 12 | { 13 | public abstract class IncapsulationTests : TestKit.Xunit2.TestKit 14 | { 15 | protected IncapsulationTests(Type serializerType) : base(ConfigFactory.GetConfig(serializerType)) 16 | { 17 | } 18 | 19 | [Fact] 20 | public virtual void Can_Serialize_internal_class() 21 | { 22 | var internalClass = new IncapsulationMessages.InternalClass(55); 23 | AssertEqual(internalClass); 24 | } 25 | 26 | [Fact] 27 | public virtual void Can_Serialize_a_class_with_internal_constructor() 28 | { 29 | var classWithInternalConstructor = new IncapsulationMessages.ClassWithInternalConstructor(55); 30 | AssertEqual(classWithInternalConstructor); 31 | } 32 | 33 | [Fact] 34 | public virtual void Can_Serialize_a_class_with_private_constructor() 35 | { 36 | var privateConstructor = IncapsulationMessages.ClassWithPrivateConstructor.Instance; 37 | AssertEqual(privateConstructor); 38 | } 39 | 40 | protected T AssertAndReturn(T message) 41 | { 42 | var serializer = Sys.Serialization.FindSerializerFor(message); 43 | var serialized = serializer.ToBinary(message); 44 | var result = serializer.FromBinary(serialized, typeof(T)); 45 | return (T)result; 46 | } 47 | 48 | protected void AssertEqual(T message) 49 | { 50 | var deserialized = AssertAndReturn(message); 51 | Assert.Equal(message, deserialized); 52 | } 53 | } 54 | 55 | public static class IncapsulationMessages 56 | { 57 | internal class InternalClass 58 | { 59 | public InternalClass(int number) 60 | { 61 | Number = number; 62 | } 63 | 64 | public int Number { get; } 65 | 66 | protected bool Equals(InternalClass other) => Number == other.Number; 67 | 68 | public override bool Equals(object obj) 69 | { 70 | if (ReferenceEquals(null, obj)) return false; 71 | if (ReferenceEquals(this, obj)) return true; 72 | if (obj.GetType() != this.GetType()) return false; 73 | return Equals((InternalClass)obj); 74 | } 75 | 76 | public override int GetHashCode() => Number; 77 | } 78 | 79 | public class ClassWithPrivateConstructor 80 | { 81 | public static ClassWithPrivateConstructor Instance { get; } = new ClassWithPrivateConstructor(); 82 | private ClassWithPrivateConstructor() { } 83 | public override bool Equals(object obj) => obj is ClassWithPrivateConstructor; 84 | } 85 | 86 | public class ClassWithInternalConstructor 87 | { 88 | internal ClassWithInternalConstructor(int number) 89 | { 90 | Number = number; 91 | } 92 | 93 | public int Number { get; } 94 | 95 | protected bool Equals(ClassWithInternalConstructor other) => Number == other.Number; 96 | 97 | public override bool Equals(object obj) 98 | { 99 | if (ReferenceEquals(null, obj)) return false; 100 | if (ReferenceEquals(this, obj)) return true; 101 | if (obj.GetType() != this.GetType()) return false; 102 | return Equals((ClassWithInternalConstructor)obj); 103 | } 104 | 105 | public override int GetHashCode() => Number; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Akka.Serialization.Testkit/CustomMessagesTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using FluentAssertions; 8 | using System; 9 | using Akka.Serialization.Testkit.Util; 10 | using Xunit; 11 | 12 | namespace Akka.Serialization.Testkit 13 | { 14 | public abstract class CustomMessagesTests : TestKit.Xunit2.TestKit 15 | { 16 | protected CustomMessagesTests(Type serializerType) : base(ConfigFactory.GetConfig(serializerType)) 17 | { 18 | } 19 | 20 | [Fact] 21 | public virtual void Can_Serialize_EmptyMessage() 22 | { 23 | var message = new CustomMessage.EmptyMessage(); 24 | AssertAndReturn(message).Should().BeOfType(); 25 | } 26 | 27 | [Fact] 28 | public void Can_Serialize_MessageWithPublicSetters() 29 | { 30 | var actual = new CustomMessage.MessageWithPublicSetters() 31 | { 32 | Name = "John", 33 | Age = 15 34 | }; 35 | AssertEqual(actual); 36 | } 37 | 38 | [Fact] 39 | public virtual void Can_Serialize_MessageWithReadonlyFields() 40 | { 41 | var actual = new CustomMessage.MessageWithPublicFields(); 42 | actual.Name = "John"; 43 | actual.Age = 15; 44 | AssertEqual(actual); 45 | } 46 | 47 | protected T AssertAndReturn(T message) 48 | { 49 | var serializer = Sys.Serialization.FindSerializerFor(message); 50 | var serialized = serializer.ToBinary(message); 51 | var result = serializer.FromBinary(serialized, typeof(T)); 52 | return (T)result; 53 | } 54 | 55 | protected void AssertEqual(T message) 56 | { 57 | var deserialized = AssertAndReturn(message); 58 | Assert.Equal(message, deserialized); 59 | } 60 | } 61 | 62 | public static class CustomMessage 63 | { 64 | public class EmptyMessage { } 65 | 66 | public class MessageWithPublicSetters 67 | { 68 | public int Age { get; set; } 69 | 70 | public string Name { get; set; } 71 | 72 | private bool Equals(MessageWithPublicSetters other) 73 | { 74 | return String.Equals(Name, (string)other.Name) && Age == other.Age; 75 | } 76 | 77 | public override bool Equals(object obj) 78 | { 79 | if (ReferenceEquals(null, obj)) return false; 80 | if (ReferenceEquals(this, obj)) return true; 81 | return obj is MessageWithPublicSetters && Equals((MessageWithPublicSetters)obj); 82 | } 83 | 84 | public override int GetHashCode() 85 | { 86 | unchecked 87 | { 88 | return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Age; 89 | } 90 | } 91 | } 92 | 93 | public class MessageWithPublicFields 94 | { 95 | public int Age; 96 | 97 | public string Name; 98 | 99 | private bool Equals(MessageWithPublicFields other) 100 | { 101 | return string.Equals(Name, other.Name) && Age == other.Age; 102 | } 103 | 104 | public override bool Equals(object obj) 105 | { 106 | if (ReferenceEquals(null, obj)) return false; 107 | if (ReferenceEquals(this, obj)) return true; 108 | return obj is MessageWithPublicFields && Equals((MessageWithPublicFields)obj); 109 | } 110 | 111 | public override int GetHashCode() 112 | { 113 | unchecked 114 | { 115 | return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Age; 116 | } 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Akka.Serialization.Testkit/CollectionsTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Collections.Immutable; 10 | using System.Linq; 11 | using Akka.Serialization.Testkit.Util; 12 | using Xunit; 13 | 14 | namespace Akka.Serialization.Testkit 15 | { 16 | public abstract class CollectionsTests : TestKit.Xunit2.TestKit 17 | { 18 | protected CollectionsTests(Type serializerType) : base(ConfigFactory.GetConfig(serializerType)) 19 | { 20 | } 21 | 22 | [Fact] 23 | public void Can_Serialize_Array() 24 | { 25 | var actual = new[] { 5, 7, 856, 34 }; 26 | AssertEqual(actual); 27 | } 28 | 29 | [Fact] 30 | public void Can_Serialize_List() 31 | { 32 | var actual = new List() { 5, 7, 856, 34 }; 33 | AssertEqual(actual); 34 | } 35 | 36 | [Fact] 37 | public void Can_Serialize_LinkedList() 38 | { 39 | var actual = new LinkedList(); 40 | actual.AddLast(5); 41 | actual.AddLast(122); 42 | actual.AddLast(25); 43 | AssertEqual(actual); 44 | } 45 | 46 | [Fact] 47 | public void Can_Serialize_Stack() 48 | { 49 | var actual = new Stack(); 50 | actual.Push(5); 51 | actual.Push(122); 52 | actual.Push(25); 53 | AssertEqual(actual); 54 | } 55 | 56 | [Fact] 57 | public void Can_Serialize_Queue() 58 | { 59 | var actual = new Queue(); 60 | actual.Enqueue(5); 61 | actual.Enqueue(122); 62 | actual.Enqueue(25); 63 | AssertEqual(actual); 64 | } 65 | 66 | [Fact] 67 | public void Can_Serialize_HashSet() 68 | { 69 | var actual = new HashSet() { 5, 7, 856, 34 }; 70 | AssertEqual(actual); 71 | } 72 | 73 | [Fact] 74 | public void Can_Serialize_Dictionary() 75 | { 76 | var actual = new Dictionary() 77 | { 78 | [1] = "one", 79 | [2] = "two" 80 | }; 81 | AssertEqual(actual); 82 | } 83 | 84 | // Immutable 85 | [Fact] 86 | public void Can_Serialize_ImmutableArray() 87 | { 88 | ImmutableArray expected = new[] { 5, 7, 856, 34 }.ToImmutableArray(); 89 | AssertAndReturn(expected).SequenceEqual(expected); 90 | } 91 | 92 | [Fact] 93 | public void Can_Serialize_ImmutableList() 94 | { 95 | var actual = new List() { 5, 7, 856, 34 }.ToImmutableList(); 96 | AssertEqual(actual); 97 | } 98 | 99 | [Fact] 100 | public void Can_Serialize_ImmutableHashSet() 101 | { 102 | var actual = new HashSet() { 5, 7, 856, 34 }.ToImmutableHashSet(); 103 | AssertEqual(actual); 104 | } 105 | 106 | [Fact] 107 | public void Can_Serialize_ImmutableDictionary() 108 | { 109 | var actual = new Dictionary() 110 | { 111 | [1] = "one", 112 | [2] = "two" 113 | }.ToImmutableDictionary(); 114 | AssertEqual(actual); 115 | } 116 | 117 | protected T AssertAndReturn(T message) 118 | { 119 | var serializer = Sys.Serialization.FindSerializerFor(message); 120 | var serialized = serializer.ToBinary(message); 121 | var result = serializer.FromBinary(serialized, typeof(T)); 122 | return (T)result; 123 | } 124 | 125 | protected void AssertEqual(T message) 126 | { 127 | var deserialized = AssertAndReturn(message); 128 | Assert.Equal(message, deserialized); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/MsgPackSerializerSettings.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using Akka.Configuration; 11 | using Akka.Util; 12 | using MessagePack; 13 | 14 | namespace Akka.Serialization.MessagePack 15 | { 16 | public class MsgPackSerializerSettings 17 | { 18 | public enum Lz4Settings 19 | { 20 | None, 21 | Lz4Block, 22 | Lz4BlockArray 23 | } 24 | public MsgPackSerializerSettings(Lz4Settings enableLz4Compression) : this( 25 | enableLz4Compression, Array.Empty(), Array.Empty(), true,true,false) 26 | { 27 | 28 | } 29 | 30 | public Lz4Settings EnableLz4Compression { get; } 31 | public IEnumerable OverrideConverters { get;} 32 | public IEnumerable Converters { get;} 33 | public bool OmitAssemblyVersion { get; } 34 | public bool AllowAssemblyVersionMismatch { get; } 35 | public bool UseOldFormatterCompatibility { get; } 36 | 37 | public static readonly MsgPackSerializerSettings Default = new MsgPackSerializerSettings( 38 | enableLz4Compression: Lz4Settings.None); 39 | 40 | private MsgPackSerializerSettings(Lz4Settings enableLz4Compression, IEnumerable overrideConverterTypes, IEnumerable converterTypes, bool allowAssemblyVersionMismatch, bool omitAssemblyVersion, bool useOldFormatterCompatibility) 41 | { 42 | EnableLz4Compression = enableLz4Compression; 43 | OverrideConverters = overrideConverterTypes.ToArray(); 44 | Converters = converterTypes.ToArray(); 45 | AllowAssemblyVersionMismatch = allowAssemblyVersionMismatch; 46 | OmitAssemblyVersion = omitAssemblyVersion; 47 | UseOldFormatterCompatibility = useOldFormatterCompatibility; 48 | } 49 | 50 | public static MsgPackSerializerSettings Create(Config config) 51 | { 52 | if (config == null) throw new ArgumentNullException(nameof(config), "MsgPackSerializerSettings require a config, default path: `akka.serializers.msgpack`"); 53 | 54 | return new MsgPackSerializerSettings( 55 | enableLz4Compression: GetLz4Settings(config), 56 | GetOverrideConverterTypes(config), 57 | GetConverterTypes(config), 58 | config.GetBoolean("allow-assembly-version-mismatch", true), 59 | config.GetBoolean("omit-assembly-version", true), 60 | config.GetBoolean("use-old-formatter-compatibility", false)); 61 | } 62 | 63 | private static Lz4Settings GetLz4Settings(Config config) 64 | { 65 | var str = config.GetString("enable-lz4-compression", "none"); 66 | switch (str.ToLower()) 67 | { 68 | case "lz4block": 69 | return Lz4Settings.Lz4Block; 70 | case "lz4array": 71 | return Lz4Settings.Lz4BlockArray; 72 | default: 73 | return Lz4Settings.None; 74 | } 75 | } 76 | 77 | private static IEnumerable GetOverrideConverterTypes(Config config) 78 | { 79 | var converterNames = config.GetStringList("converters-override", new string[] { }); 80 | 81 | if (converterNames != null) 82 | foreach (var converterName in converterNames) 83 | { 84 | var type = Type.GetType(converterName, true); 85 | if (!typeof(IFormatterResolver).IsAssignableFrom(type)) 86 | throw new ArgumentException( 87 | $"Type {type} doesn't inherit from a {typeof(IFormatterResolver)}"); 88 | 89 | yield return type; 90 | } 91 | } 92 | 93 | private static IEnumerable GetConverterTypes(Config config) 94 | { 95 | var converterNames = config.GetStringList("converters", new string[] { }); 96 | 97 | if (converterNames != null) 98 | foreach (var converterName in converterNames) 99 | { 100 | var type = Type.GetType(converterName, true); 101 | if (!typeof(IFormatterResolver).IsAssignableFrom(type)) 102 | throw new ArgumentException( 103 | $"Type {type} doesn't inherit from a {typeof(IFormatterResolver)}"); 104 | 105 | yield return type; 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Akka.Serialization.MessagePack 2 | === 3 | [![Build status](https://ci.appveyor.com/api/projects/status/xaltap7v4n0m042d/branch/dev?svg=true)](https://ci.appveyor.com/project/akkadotnet-contrib/akka-serialization-messagepack/branch/dev) [![NuGet Version](http://img.shields.io/nuget/v/Akka.Serialization.MessagePack.svg?style=flat)](https://www.nuget.org/packages/Akka.Serialization.MessagePack/) 4 | 5 | Akka.NET serialization with [MessagePack](https://github.com/neuecc/MessagePack-CSharp) 6 | 7 | The `Akka.Serialization.MessagePack` plugin is designed to: 8 | 9 | - Be Low Ceremony for common cases. 10 | - Allow flexiblity for advanced cases. 11 | - Provide High Performance 12 | - Allow for Optional LZ4 compaction. 13 | 14 | ### Payload Versioning Notes 15 | 16 | #### Assembly Versions 17 | This plugin has a few different options to control payload serialization/deserialization: 18 | 19 | - `allow-assembly-version-mismatch` 20 | - If `False`, Assembly version mismatches between payload and availiable deserializer will throw. 21 | - Default is `True` 22 | - `omit-assembly-version` 23 | - Controls whether Assembly version is included in payload. 24 | 25 | 26 | #### Schema Evolution 27 | 28 | One *can* do a form of 'Schema evolution' (if you squint at it from the right angle) with MessagePack payload definitions. 29 | 30 | Consider the following: 31 | 32 | ```csharp 33 | [MessagePackObject(false)] 34 | public class WireType 35 | { 36 | [Key(0)] 37 | public int Foo {get;set;} 38 | [Key(1)] 39 | public string Bar {get;set;} 40 | [Key(2)] 41 | public byte[] Bin {get;set;} 42 | } 43 | ``` 44 | 45 | The above uses Messagepack attributes to define that every item is packed into an array. 46 | 47 | If one wanted to add a new field, they could do, for example, 48 | 49 | ```csharp 50 | [MessagePackObject(false)] 51 | public class WireType 52 | { 53 | [Key(0)] 54 | public int Foo {get;set;} 55 | [Key(1)] 56 | public string Bar {get;set;} 57 | [Key(2)] 58 | public byte[] Bin {get;set;} 59 | [Key(3)] 60 | public byte[]? OtherBin {get;set;} 61 | } 62 | ``` 63 | 64 | The consumer will, of course, need to do it's own checks for whether `OtherBin` is null, however the serializer itself will be able to handle using/ignoring data appropriately. 65 | 66 | Two additional notes: 67 | 68 | 1. Any empty slots will be filled with nil up to the max Key in the array. So, in the above example, if `OtherBin` had a Key index of 64, it would be serialized as a 64 slot array with 60 `nil` values between `Bin` and `OtherBin`. 69 | 2. One can apply inheritance and DU-style behavior via attributes, see the MP docs for details. 70 | 71 | ## How to setup MessagePack as default serializer 72 | Bind MessagePack serializer using following HOCON configuration in your actor system settings: 73 | ```hocon 74 | akka { 75 | actor { 76 | serializers { 77 | messagepack = "Akka.Serialization.MessagePack.MsgPackSerializer, Akka.Serialization.MessagePack" 78 | } 79 | serialization-bindings { 80 | "System.Object" = messagepack 81 | } 82 | } 83 | } 84 | ``` 85 | 86 | ## Benchmarks 87 | ``` ini 88 | 89 | BenchmarkDotNet=v0.10.9, OS=Windows 10 Redstone 2 (10.0.15063) 90 | Processor=Intel Core i5-6400 CPU 2.70GHz (Skylake), ProcessorCount=4 91 | Frequency=2648439 Hz, Resolution=377.5809 ns, Timer=TSC 92 | .NET Core SDK=2.0.0 93 | [Host] : .NET Core 2.0.0 (Framework 4.6.00001.0), 64bit RyuJIT 94 | NETCORE 2.0 : .NET Core 2.0.0 (Framework 4.6.00001.0), 64bit RyuJIT 95 | 96 | Job=NETCORE 2.0 Platform=X64 Runtime=Core 97 | Server=True Toolchain=CoreCsProj 98 | 99 | ``` 100 | | Method | Mean | Error | StdDev | Gen 0 | Allocated | 101 | |------------------------------------------------------- |------------:|-----------:|-----------:|-------:|----------:| 102 | | MsgPack_serialize_string | 248.7 ns | 2.221 ns | 2.078 ns | 0.0029 | 112 B | 103 | | Hyperion_serialize_string | 414.2 ns | 5.646 ns | 5.281 ns | 0.0257 | 832 B | 104 | | JsonNet_serialize_string | 1,854.9 ns | 36.749 ns | 43.748 ns | 0.1355 | 4336 B | 105 | | MsgPack_serialize_SimpleObject | 351.6 ns | 5.425 ns | 5.074 ns | 0.0037 | 136 B | 106 | | Hyperion_serialize_SimpleObject | 965.3 ns | 12.820 ns | 11.992 ns | 0.0331 | 1112 B | 107 | | JsonNet_serialize_SimpleObject | 23,832.4 ns | 339.575 ns | 317.639 ns | 0.4008 | 14576 B | 108 | | MsgPack_serialize_SimpleOptimizedObject_int_keys | 200.1 ns | 1.425 ns | 1.333 ns | 0.0019 | 72 B | 109 | | Hyperion_serialize_SimpleOptimizedObject_preregistered | 493.4 ns | 6.110 ns | 5.715 ns | 0.0216 | 712 B | 110 | | MsgPack_serialize_TypelessObject | 2,096.3 ns | 21.150 ns | 19.784 ns | 0.0120 | 568 B | 111 | | Hyperion_serialize_TypelessObject | 5,698.9 ns | 34.648 ns | 32.410 ns | 0.1465 | 4952 B | 112 | | JsonNet_serialize_TypelessObject | 42,583.6 ns | 291.306 ns | 258.235 ns | 0.3342 | 13960 B | 113 | 114 | 115 | ## Maintainer 116 | - [alexvaluyskiy](https://github.com/alexvaluyskiy) 117 | -------------------------------------------------------------------------------- /Akka.Serialization.Testkit/PrimitiveSerializerTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Numerics; 9 | using Akka.Serialization.Testkit.Util; 10 | using Xunit; 11 | 12 | namespace Akka.Serialization.Testkit 13 | { 14 | public abstract class PrimitiveSerializerTests : TestKit.Xunit2.TestKit 15 | { 16 | protected PrimitiveSerializerTests(Type serializerType) : base(ConfigFactory.GetConfig(serializerType)) 17 | { 18 | } 19 | 20 | [Fact] 21 | public void Can_Serialize_string() 22 | { 23 | string actual = "example-string"; 24 | AssertEqual(actual); 25 | } 26 | 27 | [Fact] 28 | public void Can_Serialize_bool() 29 | { 30 | var actual = false; 31 | AssertEqual(actual); 32 | } 33 | 34 | [Fact] 35 | public void Can_Serialize_byte() 36 | { 37 | byte actual = 12; 38 | AssertEqual(actual); 39 | } 40 | 41 | [Fact] 42 | public void Can_Serialize_int() 43 | { 44 | int actual = 435; 45 | AssertEqual(actual); 46 | } 47 | 48 | [Fact] 49 | public void Can_Serialize_long() 50 | { 51 | long actual = 435L; 52 | AssertEqual(actual); 53 | } 54 | 55 | [Fact] 56 | public void Can_Serialize_double() 57 | { 58 | double actual = 56.56d; 59 | AssertEqual(actual); 60 | } 61 | 62 | [Fact] 63 | public void Can_Serialize_float() 64 | { 65 | float actual = 56.56f; 66 | AssertEqual(actual); 67 | } 68 | 69 | [Fact] 70 | public void Can_Serialize_decimal() 71 | { 72 | decimal actual = 56.56M; 73 | AssertEqual(actual); 74 | } 75 | 76 | [Fact] 77 | public void Can_Serialize_BigInteger() 78 | { 79 | BigInteger actual = 333333333333333; 80 | AssertEqual(actual); 81 | } 82 | 83 | [Fact] 84 | public void Can_Serialize_Nullable() 85 | { 86 | int? actual = 5; 87 | AssertEqual(actual); 88 | } 89 | 90 | [Fact] 91 | public void Can_Serialize_NullableWithNull() 92 | { 93 | int? actual = null; 94 | AssertEqual(actual); 95 | } 96 | 97 | [Fact] 98 | public virtual void Can_Serialize_DateTime() 99 | { 100 | DateTime date = new DateTime(2016, 6, 7, 15, 6, 45); 101 | AssertEqual(date); 102 | } 103 | 104 | [Fact] 105 | public void Can_Serialize_DateTimeUtc() 106 | { 107 | DateTime date = new DateTime(2016, 6, 7, 15, 6, 45, DateTimeKind.Utc); 108 | AssertEqual(date); 109 | } 110 | 111 | [Fact] 112 | public void Can_Serialize_DateTimeOffset() 113 | { 114 | DateTimeOffset date = new DateTimeOffset(2016, 6, 7, 15, 6, 45, TimeSpan.FromSeconds(3600)); 115 | AssertEqual(date); 116 | } 117 | 118 | [Fact] 119 | public void Can_Serialize_TimeSpan() 120 | { 121 | TimeSpan timeSpan = TimeSpan.FromSeconds(3658); 122 | AssertEqual(timeSpan); 123 | } 124 | 125 | [Fact] 126 | public void Can_Serialize_Tuple() 127 | { 128 | var tuple = Tuple.Create(25, "tuple"); 129 | AssertEqual(tuple); 130 | } 131 | 132 | [Fact] 133 | public virtual void Can_Serialize_ValueTuple() 134 | { 135 | var valueTuple = new ValueTuple(4, "name"); 136 | AssertEqual(valueTuple); 137 | } 138 | 139 | [Fact] 140 | public virtual void Can_Serialize_NamedValueTuple() 141 | { 142 | var valueTuple = (age: 4, name: "25"); 143 | AssertEqual(valueTuple); 144 | } 145 | 146 | [Fact] 147 | public void Can_Serialize_Guid() 148 | { 149 | var guid = Guid.NewGuid(); 150 | AssertEqual(guid); 151 | } 152 | 153 | [Fact] 154 | public virtual void Can_Serialize_AbsoluteUri() 155 | { 156 | var actual = new Uri("http://getakka.net/articles/index.html"); 157 | AssertEqual(actual); 158 | } 159 | 160 | [Fact] 161 | public virtual void Can_Serialize_RelativeUri() 162 | { 163 | var actual = new Uri("/articles/index.html", UriKind.Relative); 164 | AssertEqual(actual); 165 | } 166 | 167 | protected T AssertAndReturn(T message) 168 | { 169 | var serializer = Sys.Serialization.FindSerializerFor(message); 170 | var serialized = serializer.ToBinary(message); 171 | var result = serializer.FromBinary(serialized, typeof(T)); 172 | return (T)result; 173 | } 174 | 175 | protected void AssertEqual(T message) 176 | { 177 | var deserialized = AssertAndReturn(message); 178 | Assert.Equal(message, deserialized); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Custom 2 | tools/ 3 | .nuget/ 4 | .dotnet/ 5 | .idea/ 6 | .[Dd][Ss]_[Ss]tore 7 | 8 | ## NBench output 9 | [Pp]erf[Rr]esult*/ 10 | 11 | ## Ignore Visual Studio temporary files, build results, and 12 | ## files generated by popular Visual Studio add-ons. 13 | ## 14 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 15 | 16 | # User-specific files 17 | *.suo 18 | *.user 19 | *.userosscache 20 | *.sln.docstates 21 | 22 | # User-specific files (MonoDevelop/Xamarin Studio) 23 | *.userprefs 24 | 25 | # VS Code files 26 | .vscode/ 27 | 28 | # Build results 29 | [Dd]ebug/ 30 | [Dd]ebugPublic/ 31 | [Rr]elease/ 32 | [Rr]eleases/ 33 | x64/ 34 | x86/ 35 | bld/ 36 | [Bb]in/ 37 | [Oo]bj/ 38 | [Ll]og/ 39 | 40 | #FAKE 41 | .fake 42 | tools/ 43 | 44 | #DocFx output 45 | _site/ 46 | 47 | # Visual Studio 2015 cache/options directory 48 | .vs/ 49 | # Uncomment if you have tasks that create the project's static files in wwwroot 50 | #wwwroot/ 51 | 52 | # MSTest test Results 53 | [Tt]est[Rr]esult*/ 54 | [Bb]uild[Ll]og.* 55 | 56 | # NUNIT 57 | *.VisualState.xml 58 | TestResult.xml 59 | 60 | # Build Results of an ATL Project 61 | [Dd]ebugPS/ 62 | [Rr]eleasePS/ 63 | dlldata.c 64 | 65 | # .NET Core 66 | project.lock.json 67 | project.fragment.lock.json 68 | artifacts/ 69 | **/Properties/launchSettings.json 70 | 71 | *_i.c 72 | *_p.c 73 | *_i.h 74 | *.ilk 75 | *.meta 76 | *.obj 77 | *.pch 78 | *.pdb 79 | *.pgc 80 | *.pgd 81 | *.rsp 82 | *.sbr 83 | *.tlb 84 | *.tli 85 | *.tlh 86 | *.tmp 87 | *.tmp_proj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # TFS 2012 Local Workspace 117 | $tf/ 118 | 119 | # Guidance Automation Toolkit 120 | *.gpState 121 | 122 | # ReSharper is a .NET coding add-in 123 | _ReSharper*/ 124 | *.[Rr]e[Ss]harper 125 | *.DotSettings.user 126 | 127 | # JustCode is a .NET coding add-in 128 | .JustCode 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # TODO: Comment the next line if you want to checkin your web deploy settings 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/packages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/packages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/packages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Since there are multiple workflows, uncomment next line to ignore bower_components 225 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 226 | #bower_components/ 227 | 228 | # RIA/Silverlight projects 229 | Generated_Code/ 230 | 231 | # Backup & report files from converting an old project file 232 | # to a newer Visual Studio version. Backup files are not needed, 233 | # because we have git ;-) 234 | _UpgradeReport_Files/ 235 | Backup*/ 236 | UpgradeLog*.XML 237 | UpgradeLog*.htm 238 | 239 | # SQL Server files 240 | *.mdf 241 | *.ldf 242 | *.ndf 243 | 244 | # Business Intelligence projects 245 | *.rdl.data 246 | *.bim.layout 247 | *.bim_*.settings 248 | 249 | # Microsoft Fakes 250 | FakesAssemblies/ 251 | 252 | # GhostDoc plugin setting file 253 | *.GhostDoc.xml 254 | 255 | # Node.js Tools for Visual Studio 256 | .ntvs_analysis.dat 257 | node_modules/ 258 | 259 | # Typescript v1 declaration files 260 | typings/ 261 | 262 | # Visual Studio 6 build log 263 | *.plg 264 | 265 | # Visual Studio 6 workspace options file 266 | *.opt -------------------------------------------------------------------------------- /Akka.Serialization.Testkit/ExceptionsTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using FluentAssertions; 8 | using System; 9 | using System.Runtime.Serialization; 10 | using Akka.Serialization.Testkit.Util; 11 | using Xunit; 12 | 13 | namespace Akka.Serialization.Testkit 14 | { 15 | public abstract class ExceptionsTests : TestKit.Xunit2.TestKit 16 | { 17 | protected ExceptionsTests(Type serializerType) : base(ConfigFactory.GetConfig(serializerType)) 18 | { 19 | } 20 | 21 | [Fact] 22 | public virtual void Can_Serialize_Exception() 23 | { 24 | var exception = new SampleExceptions.BasicException(); 25 | AssertAndReturn(exception).Should().BeOfType(); 26 | } 27 | 28 | [Fact] 29 | public virtual void Can_Serialize_ExceptionWithMessage() 30 | { 31 | var expected = new SampleExceptions.BasicException("Some message"); 32 | var actual = AssertAndReturn(expected); 33 | AssertException(expected, actual); 34 | } 35 | 36 | [Fact] 37 | public virtual void Can_Serialize_ExceptionWithMessageAndInnerException() 38 | { 39 | var expected = new SampleExceptions.BasicException("Some message", new ArgumentNullException()); 40 | var actual = AssertAndReturn(expected); 41 | AssertException(expected, actual); 42 | } 43 | 44 | [Fact] 45 | public virtual void Can_Serialize_ExceptionWithStackTrace() 46 | { 47 | try 48 | { 49 | throw new SampleExceptions.BasicException(); 50 | } 51 | catch (SampleExceptions.BasicException ex) 52 | { 53 | var actual = AssertAndReturn(ex); 54 | AssertException(ex, actual); 55 | } 56 | } 57 | 58 | [Fact] 59 | public virtual void Can_Serialize_ExceptionWithCustomFields() 60 | { 61 | var exception = new SampleExceptions.ExceptionWithCustomFields("Some message", "John", 16); 62 | var actual = AssertAndReturn(exception); 63 | AssertException(exception, actual); 64 | actual.Name.Should().Be(exception.Name); 65 | actual.Age.Should().Be(exception.Age); 66 | } 67 | 68 | private void AssertException(Exception expected, Exception actual) 69 | { 70 | if (expected == null && actual == null) return; 71 | actual.Should().BeOfType(expected.GetType()); 72 | actual.Message.Should().Be(expected.Message); 73 | actual.StackTrace.Should().Be(expected.StackTrace); 74 | actual.Source.Should().Be(expected.Source); 75 | AssertException(expected.InnerException, actual.InnerException); 76 | } 77 | 78 | protected T AssertAndReturn(T message) 79 | { 80 | var serializer = Sys.Serialization.FindSerializerFor(message); 81 | var serialized = serializer.ToBinary(message); 82 | var result = serializer.FromBinary(serialized, typeof(T)); 83 | return (T)result; 84 | } 85 | 86 | protected void AssertEqual(T message) 87 | { 88 | var deserialized = AssertAndReturn(message); 89 | Assert.Equal(message, deserialized); 90 | } 91 | } 92 | 93 | public static class SampleExceptions 94 | { 95 | public class BasicException : Exception 96 | { 97 | public BasicException() 98 | { 99 | } 100 | 101 | public BasicException(string message) : base(message) 102 | { 103 | } 104 | 105 | public BasicException(string message, Exception innerException) : base(message, innerException) 106 | { 107 | } 108 | 109 | protected BasicException(SerializationInfo info, StreamingContext context) : base(info, context) 110 | { 111 | } 112 | } 113 | 114 | public class ExceptionWithCustomFields : Exception 115 | { 116 | public ExceptionWithCustomFields() 117 | { 118 | } 119 | 120 | public ExceptionWithCustomFields(string message, string name, int age) 121 | : this(message, name, age, null) 122 | { 123 | 124 | } 125 | 126 | public ExceptionWithCustomFields(string message, string name, int age, Exception innerException) 127 | : base(message, innerException) 128 | { 129 | Name = name; 130 | Age = age; 131 | } 132 | 133 | protected ExceptionWithCustomFields(SerializationInfo info, StreamingContext context) : base(info, context) 134 | { 135 | Name = info.GetString("Name"); 136 | Age = info.GetInt32("Age"); 137 | } 138 | 139 | public string Name { get; set; } 140 | 141 | public int Age { get; set; } 142 | 143 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 144 | { 145 | if (info == null) 146 | { 147 | throw new ArgumentNullException(nameof(info)); 148 | } 149 | 150 | info.AddValue("Name", Name); 151 | info.AddValue("Age", Age); 152 | base.GetObjectData(info, context); 153 | } 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack.Tests/MsgPackVersionToleranceTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Text; 9 | using Akka.Actor; 10 | using Akka.Serialization.Testkit.Util; 11 | using FluentAssertions; 12 | using MessagePack; 13 | using MessagePack.Resolvers; 14 | using Xunit; 15 | 16 | namespace Akka.Serialization.MessagePack.Tests 17 | { 18 | public class MsgPackVersionToleranceTests : TestKit.Xunit2.TestKit 19 | { 20 | public MsgPackVersionToleranceTests() : base(ConfigFactory.GetConfig(typeof(MsgPackSerializer))) 21 | { 22 | } 23 | 24 | [Fact] 25 | public void Can_deserialize_Uri_message_which_was_serialized_on_full_NET() 26 | { 27 | var ser = new MsgPackSerializer((ExtendedActorSystem)Sys, 28 | new MsgPackSerializerSettings(MsgPackSerializerSettings.Lz4Settings.None)); 29 | // TypelessClass with System.URI serialized on .NET 30 | var serializedString = "gaZOZXN0ZWTJAAAAbWTZVVN5c3RlbS5VcmksIFN5c3RlbSwgVmVyc2lvbj00LjAuMC4wLCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODm1aHR0cDovL21pY3Jvc29mdC5jb20v"; 31 | 32 | var deserialized = (TypelessClass)ser.FromBinary( 33 | Convert.FromBase64String(serializedString), 34 | typeof(TypelessClass)); 35 | Assert.Equal(new Uri("http://microsoft.com"), deserialized.Nested); 36 | } 37 | 38 | [Fact] 39 | public void Can_deserialize_TimeSpan_message_which_was_serialized_on_full_NET() 40 | { 41 | var ser = new MsgPackSerializer((ExtendedActorSystem)Sys, 42 | new MsgPackSerializerSettings(MsgPackSerializerSettings.Lz4Settings.None)); 43 | // TypelessClass with System.TimeSpan serialized on .NET 44 | var serializedString = "gaZOZXN0ZWTJAAAAH2S5U3lzdGVtLlRpbWVTcGFuLCBtc2NvcmxpYs6y0F4A"; 45 | 46 | var deserialized = ser.FromBinary( 47 | Convert.FromBase64String(serializedString)); 48 | Assert.Equal(TimeSpan.FromMinutes(5), deserialized.Nested); 49 | } 50 | 51 | [Fact] 52 | public void Can_ignore_unexpected_data() 53 | { 54 | var ser = new MsgPackSerializer((ExtendedActorSystem)Sys, 55 | new MsgPackSerializerSettings(MsgPackSerializerSettings.Lz4Settings.None)); 56 | var address2 = new AddressV2 { City = "New York", Country = "USA", Street = "Jr. Someone" }; 57 | 58 | byte[] serialized = ser.ToBinary(address2); 59 | var deserialized = ser.FromBinary(serialized); 60 | 61 | deserialized.Street.Should().Be(address2.Street); 62 | deserialized.City.Should().Be(address2.City); 63 | } 64 | 65 | [Fact] 66 | public void Can_tolerate_with_missing_data() 67 | { 68 | var ser = new MsgPackSerializer((ExtendedActorSystem)Sys, 69 | new MsgPackSerializerSettings(MsgPackSerializerSettings.Lz4Settings.None)); 70 | var address2 = new AddressV1 { City = "New York", Street = "Jr. Someone" }; 71 | 72 | byte[] serialized = ser.ToBinary(address2); 73 | var deserialized = (AddressV2)ser.FromBinary(serialized,typeof(AddressV2)); 74 | 75 | deserialized.Street.Should().Be(address2.Street); 76 | deserialized.City.Should().Be(address2.City); 77 | deserialized.Country.Should().BeNull(); 78 | } 79 | 80 | [Fact] 81 | public void Can_tolerate_with_missing_data_with_defaults() 82 | { 83 | var ser = new MsgPackSerializer((ExtendedActorSystem)Sys, 84 | new MsgPackSerializerSettings(MsgPackSerializerSettings.Lz4Settings.None)); 85 | var address2 = new AddressV1 { City = "New York", Street = "Jr. Someone" }; 86 | 87 | byte[] serialized = ser.ToBinary(address2); 88 | var deserialized = ser.FromBinary(serialized); 89 | 90 | deserialized.Street.Should().Be(address2.Street); 91 | deserialized.City.Should().Be(address2.City); 92 | deserialized.Country.Should().Be("Japan"); 93 | } 94 | 95 | protected T AssertAndReturn(T message) 96 | { 97 | var serializer = Sys.Serialization.FindSerializerFor(message); 98 | var serialized = serializer.ToBinary(message); 99 | var result = serializer.FromBinary(serialized, typeof(T)); 100 | return (T)result; 101 | } 102 | 103 | protected void AssertEqual(T message) 104 | { 105 | var deserialized = AssertAndReturn(message); 106 | Assert.Equal(message, deserialized); 107 | } 108 | 109 | public class TypelessClass 110 | { 111 | public object Nested { get; set; } 112 | } 113 | 114 | [MessagePackObject] 115 | public class AddressV1 116 | { 117 | [Key(0)] 118 | public string City { get; set; } 119 | 120 | [Key(1)] 121 | public string Street { get; set; } 122 | } 123 | 124 | [MessagePackObject] 125 | public class AddressV2 126 | { 127 | [Key(0)] 128 | public string City { get; set; } 129 | 130 | [Key(1)] 131 | public string Street { get; set; } 132 | 133 | [Key(2)] 134 | public string Country { get; set; } 135 | } 136 | 137 | [MessagePackObject] 138 | public class AddressV3 : IMessagePackSerializationCallbackReceiver 139 | { 140 | [Key(0)] 141 | public string City { get; set; } 142 | 143 | [Key(1)] 144 | public string Street { get; set; } 145 | 146 | [Key(2)] 147 | public string Country { get; set; } 148 | 149 | public void OnBeforeSerialize() 150 | { 151 | } 152 | 153 | public void OnAfterDeserialize() 154 | { 155 | Country = "Japan"; 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack.Benchmarks/SerializationBenchmarks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Akka.Actor; 5 | using Akka.Configuration; 6 | using Akka.Util.Internal; 7 | using BenchmarkDotNet.Attributes; 8 | using MessagePack; 9 | 10 | namespace Akka.Serialization.MessagePack.Benchmarks 11 | { 12 | [Config(typeof(MyConfig))] 13 | [MemoryDiagnoser] 14 | public class SerializationBenchmarks 15 | { 16 | private MsgPackSerializer MsgPackSerializer; 17 | private HyperionSerializer HyperionSerializer; 18 | private NewtonSoftJsonSerializer NewtonSoftJsonSerializer; 19 | 20 | private const string TestString = "simple long string"; 21 | 22 | private SimpleObject TestSimpleObject = new SimpleObject() 23 | { 24 | MyProperty1 = 1, MyProperty2 = 2, MyProperty3 = 3, MyProperty4 = 4, MyProperty5 = 5 25 | }; 26 | 27 | private SimpleOptimizedObject TestSimpleObjectOptimized = new SimpleOptimizedObject 28 | { 29 | MyProperty1 = 1, MyProperty2 = 2, MyProperty3 = 3, MyProperty4 = 4, MyProperty5 = 5 30 | }; 31 | 32 | private TypelessObject TestTypelessObject = new TypelessObject 33 | { 34 | MyProperty1 = Int16.MaxValue, MyProperty2 = Single.MaxValue, MyProperty3 = TestString, MyProperty4 = TimeSpan.FromDays(3), MyProperty5 = new Uri("http://somesite.com") 35 | }; 36 | 37 | public SerializationBenchmarks() 38 | 39 | { 40 | var config = ConfigurationFactory.ParseString("akka.suppress-json-serializer-warning=true"); 41 | var system = ActorSystem.Create("SerializationBenchmarks", config); 42 | 43 | MsgPackSerializer = new MsgPackSerializer(system.AsInstanceOf()); 44 | HyperionSerializer = new HyperionSerializer( 45 | system.AsInstanceOf(), 46 | new HyperionSerializerSettings(false, false, typeof(SimpleTypesProvider))); 47 | NewtonSoftJsonSerializer = new NewtonSoftJsonSerializer(system.AsInstanceOf()); 48 | } 49 | 50 | [Benchmark] 51 | public string MsgPack_serialize_string() 52 | { 53 | var bytes = MsgPackSerializer.ToBinary(TestString); 54 | return MsgPackSerializer.FromBinary(bytes); 55 | } 56 | 57 | [Benchmark] 58 | public string Hyperion_serialize_string() 59 | { 60 | var bytes = HyperionSerializer.ToBinary(TestString); 61 | return HyperionSerializer.FromBinary(bytes); 62 | } 63 | 64 | [Benchmark] 65 | public string JsonNet_serialize_string() 66 | { 67 | var bytes = NewtonSoftJsonSerializer.ToBinary(TestString); 68 | return NewtonSoftJsonSerializer.FromBinary(bytes); 69 | } 70 | 71 | [Benchmark] 72 | public SimpleObject MsgPack_serialize_SimpleObject() 73 | { 74 | var bytes = MsgPackSerializer.ToBinary(TestSimpleObject); 75 | return MsgPackSerializer.FromBinary(bytes); 76 | } 77 | 78 | [Benchmark] 79 | public SimpleObject Hyperion_serialize_SimpleObject() 80 | { 81 | var bytes = HyperionSerializer.ToBinary(TestSimpleObject); 82 | return HyperionSerializer.FromBinary(bytes); 83 | } 84 | 85 | [Benchmark] 86 | public SimpleObject JsonNet_serialize_SimpleObject() 87 | { 88 | var bytes = NewtonSoftJsonSerializer.ToBinary(TestSimpleObject); 89 | return NewtonSoftJsonSerializer.FromBinary(bytes); 90 | } 91 | 92 | [Benchmark] 93 | public SimpleOptimizedObject MsgPack_serialize_SimpleOptimizedObject_int_keys() 94 | { 95 | var bytes = MsgPackSerializer.ToBinary(TestSimpleObjectOptimized); 96 | return MsgPackSerializer.FromBinary(bytes); 97 | } 98 | 99 | [Benchmark] 100 | public SimpleOptimizedObject Hyperion_serialize_SimpleOptimizedObject_preregistered() 101 | { 102 | var bytes = HyperionSerializer.ToBinary(TestSimpleObjectOptimized); 103 | return HyperionSerializer.FromBinary(bytes); 104 | } 105 | 106 | [Benchmark] 107 | public TypelessObject MsgPack_serialize_TypelessObject() 108 | { 109 | var bytes = MsgPackSerializer.ToBinary(TestTypelessObject); 110 | return MsgPackSerializer.FromBinary(bytes); 111 | } 112 | 113 | [Benchmark] 114 | public TypelessObject Hyperion_serialize_TypelessObject() 115 | { 116 | var bytes = HyperionSerializer.ToBinary(TestTypelessObject); 117 | return HyperionSerializer.FromBinary(bytes); 118 | } 119 | 120 | [Benchmark] 121 | public TypelessObject JsonNet_serialize_TypelessObject() 122 | { 123 | var bytes = NewtonSoftJsonSerializer.ToBinary(TestTypelessObject); 124 | return NewtonSoftJsonSerializer.FromBinary(bytes); 125 | } 126 | } 127 | 128 | public class SimpleObject 129 | { 130 | public int MyProperty1 { get; set; } 131 | public int MyProperty2 { get; set; } 132 | public int MyProperty3 { get; set; } 133 | public int MyProperty4 { get; set; } 134 | public int MyProperty5 { get; set; } 135 | } 136 | 137 | [MessagePackObject] 138 | public class SimpleOptimizedObject 139 | { 140 | [Key(0)] 141 | public int MyProperty1 { get; set; } 142 | 143 | [Key(1)] 144 | public int MyProperty2 { get; set; } 145 | 146 | [Key(2)] 147 | public int MyProperty3 { get; set; } 148 | 149 | [Key(3)] 150 | public int MyProperty4 { get; set; } 151 | 152 | [Key(4)] 153 | public int MyProperty5 { get; set; } 154 | } 155 | 156 | public class TypelessObject 157 | { 158 | public object MyProperty1 { get; set; } 159 | public object MyProperty2 { get; set; } 160 | public object MyProperty3 { get; set; } 161 | public object MyProperty4 { get; set; } 162 | public object MyProperty5 { get; set; } 163 | } 164 | 165 | public class SimpleTypesProvider : IKnownTypesProvider 166 | { 167 | public SimpleTypesProvider(ExtendedActorSystem system) 168 | { 169 | if (system == null) 170 | throw new ArgumentNullException(nameof(system)); 171 | } 172 | 173 | public IEnumerable GetKnownTypes() => new Type[] { typeof(SimpleOptimizedObject) }; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Formatters/SurrogatedFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Concurrent; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.Serialization; 6 | using System.Text; 7 | using Akka.Actor; 8 | using Akka.Pattern; 9 | using Akka.Util; 10 | using Akka.Util.Reflection; 11 | using CommunityToolkit.HighPerformance.Buffers; 12 | using MessagePack; 13 | using MessagePack.Formatters; 14 | 15 | namespace Akka.Serialization.MessagePack.Resolvers 16 | { 17 | class SurrogatedFormatterTools 18 | { 19 | private static readonly ConcurrentDictionary deserTypeCache = 20 | new ConcurrentDictionary(); 21 | 22 | private static readonly Func 23 | typeLookupFactory = t => Type.GetType(t, true); 24 | 25 | internal static Type GetTypeForShortName(string type) 26 | { 27 | if (deserTypeCache.TryGetValue(type, out var val)) 28 | { 29 | return val; 30 | } 31 | else 32 | { 33 | return deserTypeCache.GetOrAdd(type, typeLookupFactory); 34 | } 35 | } 36 | 37 | } 38 | 39 | public sealed class MalformedPayloadException : SerializationException 40 | { 41 | public MalformedPayloadException(string message) : base(message) 42 | { 43 | 44 | } 45 | public MalformedPayloadException(string message, Exception exception) : base(message,exception) 46 | { 47 | 48 | } 49 | } 50 | 51 | public interface IDoNotUsePolymorphicFormatter 52 | { 53 | 54 | } 55 | 56 | /// 57 | /// A Formatter that handles converting Surrogated types to/from their 58 | /// Surrogate representation 59 | /// 60 | /// 61 | /// 62 | /// We more or less handle things in the current (default) case by the following: 63 | /// 64 | /// When a Surrogated type gets serialized, we get the surrogate, Find it's type, 65 | /// Then write the unversioned type name as well as the serialized Surrogate. 66 | /// Thankfully, MessagePack handles recursion of surrogates automagically. 67 | /// 68 | /// When we deserialize, we read the short type name and pull the type from a cache. 69 | /// (Internal to ) 70 | /// We then call Deserialize with the type to properly read the data. 71 | /// 72 | public class SurrogatedFormatter : IMessagePackFormatter, IDoNotUsePolymorphicFormatter 73 | where T : ISurrogated 74 | { 75 | private readonly ActorSystem _system; 76 | private static readonly StringPool _pool = new StringPool(); 77 | 78 | public SurrogatedFormatter(ActorSystem system) 79 | { 80 | _system = system; 81 | } 82 | 83 | public void Serialize(ref MessagePackWriter writer, T value, 84 | MessagePackSerializerOptions options) 85 | { 86 | if (value == null) 87 | { 88 | writer.WriteNil(); 89 | } 90 | else 91 | { 92 | var surrogate = value.ToSurrogate(_system); 93 | //Rather than relying on TypelessFormatter, it is better for us 94 | // to use the exact type formatter alongside manual serialization of type. 95 | // 96 | // TODO: See notes in deserialize about future improvements 97 | // for well known types. 98 | 99 | writer.WriteArrayHeader(3); 100 | //writer.WriteRaw(SurrogatedFormatterTools.baseArraySpan.Span); 101 | writer.WriteInt32(0); 102 | var surT = surrogate.GetType(); 103 | options.Resolver.GetFormatter() 104 | .Serialize(ref writer, surT.TypeQualifiedName(), options); 105 | MessagePackSerializer.Serialize(surT, ref writer, 106 | surrogate, options); 107 | 108 | 109 | } 110 | } 111 | 112 | public T Deserialize(ref MessagePackReader reader, 113 | MessagePackSerializerOptions options) 114 | { 115 | if (reader.TryReadNil()) 116 | { 117 | return default; 118 | } 119 | else 120 | { 121 | //TODO: Look into a way to avoid creating a string here. 122 | // 123 | //TODO: In future, we can use switching on well-known types, 124 | // alongside a 3-slot array with (int,null,realdata) 125 | // that would let us switch on int, 126 | // rather than sniff type below. 127 | int entries = reader.ReadArrayHeader(); 128 | if (entries == 3) 129 | { 130 | var typeInt = reader.ReadInt32(); 131 | 132 | var strToken = reader.ReadString(); 133 | var mainData = reader.ReadRaw(); 134 | return deserializeFallback(strToken, mainData, options); 135 | } 136 | else if (entries == 2) 137 | return readValueDefault(ref reader, options); 138 | else if (entries != 1) 139 | return readValueFallBack(ref reader, options, entries); 140 | else 141 | return throwInvalidLengthHelper(entries); 142 | } 143 | } 144 | 145 | [MethodImpl(MethodImplOptions.NoInlining)] 146 | private static T throwInvalidLengthHelper(int entries) 147 | { 148 | throw new MalformedPayloadException( 149 | "Invalid payload length on message!"); 150 | } 151 | 152 | private T readValueFallBack(ref MessagePackReader reader, 153 | MessagePackSerializerOptions options, int arraySize) 154 | { 155 | reader.Skip(); 156 | var v = readValueDefault(ref reader, options); 157 | while (arraySize > 2) 158 | { 159 | reader.Skip(); 160 | arraySize--; 161 | } 162 | 163 | return v; 164 | 165 | } 166 | 167 | private T deserializeFallback(string strToken, ReadOnlySequence mainData, MessagePackSerializerOptions options) 168 | { 169 | var deserType = 170 | SurrogatedFormatterTools.GetTypeForShortName(strToken); 171 | return (T)((ISurrogate)MessagePackSerializer.Deserialize(deserType, 172 | mainData, options)).FromSurrogate(_system); 173 | } 174 | 175 | private T readValueDefault(ref MessagePackReader reader, 176 | MessagePackSerializerOptions options) 177 | { 178 | return deserializeFallback(reader.ReadString(), reader.ReadRaw(), 179 | options); 180 | } 181 | } 182 | } -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/Formatters/BackwardsCompatibleSurrogatedFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | using System.Runtime.CompilerServices; 3 | using Akka.Actor; 4 | using Akka.Util; 5 | using CommunityToolkit.HighPerformance.Buffers; 6 | using MessagePack; 7 | using MessagePack.Formatters; 8 | 9 | namespace Akka.Serialization.MessagePack.Resolvers 10 | { 11 | /// 12 | /// A Formatter that handles converting Surrogated types to/from their 13 | /// Surrogate representation 14 | /// 15 | /// 16 | /// 17 | /// We more or less handle things in the current (default) case by the following: 18 | /// 19 | /// When a Surrogated type gets serialized, we get the surrogate, Find it's type, 20 | /// Then write the unversioned type name as well as the serialized Surrogate. 21 | /// Thankfully, MessagePack handles recursion of surrogates automagically. 22 | /// 23 | /// When we deserialize, we read the short type name and pull the type from a cache. 24 | /// (Internal to ) 25 | /// We then call Deserialize with the type to properly read the data. 26 | /// 27 | public class BackwardsCompatibleSurrogatedFormatter : IMessagePackFormatter, IDoNotUsePolymorphicFormatter 28 | where T : ISurrogated 29 | { 30 | private readonly ExtendedActorSystem _system; 31 | 32 | public BackwardsCompatibleSurrogatedFormatter(ActorSystem system) 33 | { 34 | _system = (ExtendedActorSystem)system; 35 | } 36 | 37 | public void Serialize(ref MessagePackWriter writer, T value, 38 | MessagePackSerializerOptions options) 39 | { 40 | if (value == null) 41 | { 42 | writer.WriteNil(); 43 | } 44 | else 45 | { 46 | var surrogate = value.ToSurrogate(_system); 47 | //Rather than relying on TypelessFormatter, it is better for us 48 | // to use the exact type formatter alongside manual serialization of type. 49 | // 50 | // TODO: See notes in deserialize about future improvements 51 | // for well known types. 52 | 53 | writer.WriteArrayHeader(3); 54 | //writer.WriteRaw(SurrogatedFormatterTools.baseArraySpan.Span); 55 | writer.WriteInt32(0); 56 | var surT = surrogate.GetType(); 57 | options.Resolver.GetFormatter() 58 | .Serialize(ref writer, surT.TypeQualifiedName(), options); 59 | MessagePackSerializer.Serialize(surT, ref writer, 60 | surrogate, options); 61 | 62 | 63 | } 64 | } 65 | 66 | public T Deserialize(ref MessagePackReader reader, 67 | MessagePackSerializerOptions options) 68 | { 69 | if (reader.TryReadNil()) 70 | { 71 | return default; 72 | } 73 | else 74 | { 75 | if (typeof(T) == typeof(ActorPath) 76 | || typeof(T) == typeof(ChildActorPath) 77 | || typeof(T) == typeof(RootActorPath)) 78 | { 79 | if (reader.NextMessagePackType == MessagePackType.String) 80 | { 81 | return deserializeOldPathFormat(reader, options); 82 | } 83 | } 84 | else if (typeof(T) == typeof(IActorRef) 85 | || typeof(T) == typeof(IInternalActorRef) 86 | || typeof(T) == typeof(RepointableActorRef)) 87 | { 88 | if (reader.NextMessagePackType == MessagePackType.String) 89 | { 90 | return deserializeOldRefFormat(reader, options); 91 | } 92 | } 93 | 94 | //TODO: Look into a way to avoid creating a string here. 95 | // 96 | //TODO: In future, we can use switching on well-known types, 97 | // alongside a 3-slot array with (int,null,realdata) 98 | // that would let us switch on int, 99 | // rather than sniff type below. 100 | int entries = reader.ReadArrayHeader(); 101 | if (entries == 3) 102 | { 103 | var typeInt = reader.ReadInt32(); 104 | 105 | var strToken = reader.ReadString(); 106 | var mainData = reader.ReadRaw(); 107 | return deserializeFallback(strToken, mainData, options); 108 | } 109 | else if (entries == 2) 110 | return readValueDefault(ref reader, options); 111 | else if (entries != 1) 112 | return readValueFallBack(ref reader, options, entries); 113 | else 114 | return throwInvalidLengthHelper(entries); 115 | } 116 | } 117 | 118 | private T deserializeOldPathFormat(MessagePackReader reader, MessagePackSerializerOptions options) 119 | { 120 | var pathStr = reader.ReadString(); 121 | return ActorPath.TryParse(pathStr, out var actorPath) ? (T)(object)actorPath : default; 122 | } 123 | 124 | private T deserializeOldRefFormat(MessagePackReader reader, MessagePackSerializerOptions options) 125 | { 126 | var pathStr = reader.ReadString(); 127 | return (T)((ExtendedActorSystem)_system).Provider.ResolveActorRef( 128 | pathStr); 129 | } 130 | 131 | [MethodImpl(MethodImplOptions.NoInlining)] 132 | private static T throwInvalidLengthHelper(int entries) 133 | { 134 | throw new MalformedPayloadException( 135 | "Invalid payload length on message!"); 136 | } 137 | 138 | private T readValueFallBack(ref MessagePackReader reader, 139 | MessagePackSerializerOptions options, int arraySize) 140 | { 141 | //TODO: This feels unsafe. 142 | reader.Skip(); 143 | var v = readValueDefault(ref reader, options); 144 | while (arraySize > 2) 145 | { 146 | reader.Skip(); 147 | arraySize--; 148 | } 149 | 150 | return v; 151 | 152 | } 153 | 154 | private T deserializeFallback(string strToken, ReadOnlySequence mainData, MessagePackSerializerOptions options) 155 | { 156 | var deserType = 157 | SurrogatedFormatterTools.GetTypeForShortName(strToken); 158 | return (T)((ISurrogate)MessagePackSerializer.Deserialize(deserType, 159 | mainData, options)).FromSurrogate(_system); 160 | } 161 | 162 | private T readValueDefault(ref MessagePackReader reader, 163 | MessagePackSerializerOptions options) 164 | { 165 | return deserializeFallback(reader.ReadString(), reader.ReadRaw(), 166 | options); 167 | } 168 | } 169 | } -------------------------------------------------------------------------------- /Akka.Serialization.Testkit/PolymorphismTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using Akka.Serialization.Testkit.Util; 10 | using Xunit; 11 | 12 | namespace Akka.Serialization.Testkit 13 | { 14 | public abstract class PolymorphismTests : TestKit.Xunit2.TestKit 15 | { 16 | protected PolymorphismTests(Type serializerType) : base(ConfigFactory.GetConfig(serializerType)) 17 | { 18 | } 19 | 20 | [Fact] 21 | public virtual void Can_Serialize_MessageWithObjectPropertyPrimitive() 22 | { 23 | var actual = new PolymorphismMessages.ImmutableMessageWithObjectTypes 24 | { 25 | Name = "John", 26 | Data = 435345345 27 | }; 28 | AssertEqual(actual); 29 | } 30 | 31 | [Fact] 32 | public virtual void Can_Serialize_MessageWithObjectPropertyPrimitive_do_not_downcast_to_byte() 33 | { 34 | var actual = new PolymorphismMessages.ImmutableMessageWithObjectTypes 35 | { 36 | Name = "John", 37 | Data = 125 38 | }; 39 | AssertEqual(actual); 40 | } 41 | 42 | [Fact] 43 | public virtual void Can_Serialize_MessageWithObjectPropertyComplex() 44 | { 45 | var data = new CustomMessage.MessageWithPublicSetters 46 | { 47 | Name = "John", 48 | Age = 15 49 | }; 50 | 51 | var actual = new PolymorphismMessages.ImmutableMessageWithObjectTypes 52 | { 53 | Name = "John", 54 | Data = data 55 | }; 56 | AssertEqual(actual); 57 | } 58 | 59 | [Fact] 60 | public virtual void Can_Serialize_MessageWithGenericPropertyPrimitive() 61 | { 62 | var actual = new PolymorphismMessages.ImmutableMessageWithGenericTypes 63 | { 64 | Name = "John", 65 | Data = 435345345 66 | }; 67 | AssertEqual(actual); 68 | } 69 | 70 | [Fact] 71 | public virtual void Can_Serialize_MessageWithGenericPropertyComplex() 72 | { 73 | var data = new CustomMessage.MessageWithPublicSetters 74 | { 75 | Name = "John", 76 | Age = 15 77 | }; 78 | 79 | var actual = new PolymorphismMessages.ImmutableMessageWithGenericTypes 80 | { 81 | Name = "John", 82 | Data = data 83 | }; 84 | AssertEqual(actual); 85 | } 86 | 87 | [Fact] 88 | public virtual void Can_Serialize_CollectionWithObjectTypePrimitive() 89 | { 90 | var actual = new List { 5, 7, 856, 34 }; 91 | AssertEqual(actual); 92 | } 93 | 94 | [Fact] 95 | public virtual void Can_Serialize_CollectionWithObjectTypeComplex() 96 | { 97 | var actual = new List { 98 | new CustomMessage.MessageWithPublicSetters { Name = "John", Age = 15 }, 99 | new CustomMessage.MessageWithPublicSetters { Name = "John2", Age = 16 } 100 | }; 101 | AssertEqual(actual); 102 | } 103 | 104 | [Fact] 105 | public virtual void Can_Serialize_DictionaryKeyWithObjectTypePrimitive() 106 | { 107 | var actual = new Dictionary 108 | { 109 | [5] = "Name", 110 | [6] = "Name2", 111 | [7] = "Name3", 112 | }; 113 | AssertEqual(actual); 114 | } 115 | 116 | [Fact] 117 | public virtual void Can_Serialize_DictionaryKeyWithObjectTypeComplex() 118 | { 119 | var actual = new Dictionary 120 | { 121 | [new CustomMessage.MessageWithPublicSetters { Name = "John", Age = 15 }] = "Name", 122 | [new CustomMessage.MessageWithPublicSetters { Name = "John2", Age = 16 }] = "Name2", 123 | [new CustomMessage.MessageWithPublicSetters { Name = "John2", Age = 14 }] = "Name3", 124 | }; 125 | AssertEqual(actual); 126 | } 127 | 128 | [Fact] 129 | public virtual void Can_Serialize_TupleItemWithObjectTypePrimitive() 130 | { 131 | var actual = Tuple.Create(25, "John"); 132 | AssertEqual(actual); 133 | } 134 | 135 | [Fact] 136 | public virtual void Can_Serialize_TupleItemWithObjectTypeComplex() 137 | { 138 | var actual = Tuple.Create(25, new CustomMessage.MessageWithPublicSetters { Name = "John", Age = 15 }); 139 | AssertEqual(actual); 140 | } 141 | 142 | protected T AssertAndReturn(T message) 143 | { 144 | var serializer = Sys.Serialization.FindSerializerFor(message); 145 | var serialized = serializer.ToBinary(message); 146 | var result = serializer.FromBinary(serialized, typeof(T)); 147 | return (T)result; 148 | } 149 | 150 | protected void AssertEqual(T message) 151 | { 152 | var deserialized = AssertAndReturn(message); 153 | Assert.Equal(message, deserialized); 154 | } 155 | } 156 | 157 | public static class PolymorphismMessages 158 | { 159 | public class ImmutableMessageWithObjectTypes 160 | { 161 | public string Name { get; set; } 162 | 163 | public object Data { get; set; } 164 | 165 | protected bool Equals(ImmutableMessageWithObjectTypes other) 166 | { 167 | return string.Equals(Name, other.Name) && Equals(Data, other.Data); 168 | } 169 | 170 | public override bool Equals(object obj) 171 | { 172 | if (ReferenceEquals(null, obj)) return false; 173 | if (ReferenceEquals(this, obj)) return true; 174 | if (obj.GetType() != this.GetType()) return false; 175 | return Equals((ImmutableMessageWithObjectTypes)obj); 176 | } 177 | 178 | public override int GetHashCode() 179 | { 180 | unchecked 181 | { 182 | return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ (Data != null ? Data.GetHashCode() : 0); 183 | } 184 | } 185 | } 186 | 187 | public sealed class ImmutableMessageWithGenericTypes 188 | { 189 | public string Name { get; set; } 190 | 191 | public T Data { get; set; } 192 | 193 | private bool Equals(ImmutableMessageWithGenericTypes other) 194 | { 195 | return string.Equals(Name, other.Name) && EqualityComparer.Default.Equals(Data, other.Data); 196 | } 197 | 198 | public override bool Equals(object obj) 199 | { 200 | if (ReferenceEquals(null, obj)) return false; 201 | if (ReferenceEquals(this, obj)) return true; 202 | return obj is ImmutableMessageWithGenericTypes && Equals((ImmutableMessageWithGenericTypes)obj); 203 | } 204 | 205 | public override int GetHashCode() 206 | { 207 | unchecked 208 | { 209 | return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ EqualityComparer.Default.GetHashCode(Data); 210 | } 211 | } 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /Akka.Serialization.MessagePack/MsgPackSerializer.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Reflection; 11 | using System.Threading; 12 | using Akka.Actor; 13 | using Akka.Configuration; 14 | using Akka.Serialization.MessagePack.Resolvers; 15 | using MessagePack; 16 | using MessagePack.Formatters; 17 | using MessagePack.ImmutableCollection; 18 | using MessagePack.Resolvers; 19 | using Newtonsoft.Json; 20 | 21 | namespace Akka.Serialization.MessagePack 22 | { 23 | public sealed class MsgPackSerializer : Serializer 24 | { 25 | private readonly MsgPackSerializerSettings _settings; 26 | private readonly IFormatterResolver _resolver; 27 | public readonly MessagePackSerializerOptions SerializerOptions; 28 | private readonly IFormatterResolver _polymorphicResolver; 29 | 30 | public MsgPackSerializer(ExtendedActorSystem system) : this(system, MsgPackSerializerSettings.Default) 31 | { 32 | 33 | } 34 | 35 | public MsgPackSerializer(ExtendedActorSystem system, Config config) 36 | : this(system, MsgPackSerializerSettings.Create(config)) 37 | { 38 | } 39 | 40 | 41 | 42 | /// 43 | /// Borrowed from , 44 | /// The concept is very similar if slightly more layered for perf. 45 | /// 46 | public static IFormatterResolver LoadFormatterResolverByType(Type type, ExtendedActorSystem system) 47 | { 48 | //This -should- be double checked by others, but just in case :) 49 | if (typeof(IFormatterResolver).IsAssignableFrom(type)) 50 | { 51 | //We look In this order: 52 | // - Is there a Ctor that will take ActorSystem/ExtendedActorSystem? 53 | // - Is there a Static 'Instance' Property/Field? 54 | // - Is there a Public, Parameterless Ctor? 55 | 56 | var ctors = type.GetConstructors(); 57 | var actorSystemCtorMaybe = ctors.FirstOrDefault(r => 58 | { 59 | var p = r.GetParameters(); 60 | if (p.Length == 1 && p[0].ParameterType 61 | .IsAssignableFrom(typeof(ExtendedActorSystem))) 62 | { 63 | return true; 64 | } 65 | 66 | return false; 67 | }); 68 | if (actorSystemCtorMaybe != null) 69 | { 70 | return (IFormatterResolver)actorSystemCtorMaybe.Invoke(new[] 71 | { system }); 72 | } 73 | 74 | var props = type.GetProperties(BindingFlags.Static | BindingFlags.Public); 75 | foreach (var propertyInfo in props) 76 | { 77 | if (propertyInfo.Name == "Instance") 78 | { 79 | return (IFormatterResolver)propertyInfo.GetValue(null); 80 | } 81 | } 82 | var fields = type.GetFields(BindingFlags.Static | BindingFlags.Public); 83 | foreach (var fieldInfo in fields) 84 | { 85 | if (fieldInfo.Name == "Instance") 86 | { 87 | return (IFormatterResolver)fieldInfo.GetValue(null); 88 | } 89 | } 90 | 91 | var defaultCtor = 92 | ctors.FirstOrDefault(r => r.GetParameters().Length == 0); 93 | if (defaultCtor != null) 94 | { 95 | return (IFormatterResolver)defaultCtor.Invoke(Array.Empty()); 96 | } 97 | 98 | throw new ArgumentException( 99 | $"Type {type} does not contain a static 'Instance' Property/Field, Ctor that takes ActorSystem, or Parameterless Ctor!"); 100 | } 101 | else 102 | { 103 | throw new ArgumentException( 104 | $"Type {type} is not assignable to IMessageFormatter!"); 105 | } 106 | } 107 | 108 | public MsgPackSerializer(ExtendedActorSystem system, 109 | MsgPackSerializerSettings settings) : base(system) 110 | { 111 | _settings = settings; 112 | //Set up the chain of resolvers; 113 | //First, we allow 'Overrides' that users put in at their own peril. 114 | //Then, we load our standard set of converters, which includes 115 | //Serializable(really just exceptions), 116 | //Immutable Collections, and our Surrogate Resolver 117 | //Then we add whatever custom serializers are specified, 118 | //Lastly dropping into the TypelessContractLess Resolver. 119 | // 120 | _resolver = CompositeResolver.Create( 121 | _settings.OverrideConverters.Select(t => 122 | LoadFormatterResolverByType(t, system)) 123 | .Concat(new[] 124 | { 125 | SerializableResolver.Instance, 126 | ImmutableCollectionResolver.Instance, 127 | settings.UseOldFormatterCompatibility 128 | ? (IFormatterResolver)new 129 | BackwardsCompatibleSurrogatedFormatterResolver( 130 | base.system) 131 | : new SurrogatedFormatterResolver(system) 132 | }) 133 | .Concat(_settings.Converters.Select(t => 134 | LoadFormatterResolverByType(t, system))) 135 | .Concat(new[] 136 | { 137 | TypelessContractlessStandardResolver.Instance 138 | }) 139 | .ToArray()); 140 | _polymorphicResolver = new PolymorphicFormatterResolver(_resolver); 141 | var opts = 142 | new MessagePackSerializerOptions(_polymorphicResolver); 143 | if (_settings.EnableLz4Compression == MsgPackSerializerSettings.Lz4Settings.Lz4Block) 144 | { 145 | opts = opts.WithCompression(MessagePackCompression.Lz4Block); 146 | } 147 | else if (_settings.EnableLz4Compression == 148 | MsgPackSerializerSettings.Lz4Settings.Lz4BlockArray) 149 | { 150 | opts = opts.WithCompression( 151 | MessagePackCompression.Lz4BlockArray); 152 | } 153 | 154 | opts = opts.WithAllowAssemblyVersionMismatch(_settings 155 | .AllowAssemblyVersionMismatch); 156 | opts = opts.WithOmitAssemblyVersion(_settings.OmitAssemblyVersion); 157 | //We handle type filtering via our own options set. 158 | //By doing so, the existing Typeless API will hook in, 159 | //i.e. we don't have to write our own Typeless Filter. 160 | SerializerOptions = new MessagePackTypeFilteringOptions(opts); 161 | 162 | } 163 | 164 | public override byte[] ToBinary(object obj) 165 | { 166 | { 167 | return MessagePackSerializer.Serialize(obj.GetType(), obj,SerializerOptions); 168 | } 169 | } 170 | 171 | public override object FromBinary(byte[] bytes, Type type) 172 | { 173 | return MessagePackSerializer.Deserialize(type, bytes,SerializerOptions); 174 | } 175 | 176 | public override int Identifier => 151; 177 | 178 | public override bool IncludeManifest => true; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Akka.Serialization.Testkit/ImmutableMessagesTests.cs: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2017 Akka.NET Contrib 4 | // 5 | //----------------------------------------------------------------------- 6 | 7 | using FluentAssertions; 8 | using System; 9 | using Akka.Serialization.Testkit.Util; 10 | using Xunit; 11 | 12 | namespace Akka.Serialization.Testkit 13 | { 14 | public abstract class ImmutableMessagesTests : TestKit.Xunit2.TestKit 15 | { 16 | protected ImmutableMessagesTests(Type serializerType) : base(ConfigFactory.GetConfig(serializerType)) 17 | { 18 | } 19 | 20 | [Fact] 21 | public virtual void Can_serialize_EmptySingleton() 22 | { 23 | var message = ImmutableMessages.EmptySingleton.Instance; 24 | AssertAndReturn(message).Should().BeOfType(); 25 | } 26 | 27 | [Fact] 28 | public virtual void Can_Serialize_ImmutableMessage() 29 | { 30 | var actual = new ImmutableMessages.ImmutableMessage("John", 15); 31 | AssertEqual(actual); 32 | } 33 | 34 | [Fact] 35 | public virtual void Can_Serialize_ImmutableMessageWithDefaultParameters() 36 | { 37 | var actual = new ImmutableMessages.ImmutableMessageWithDefaultParameters("John"); 38 | AssertEqual(actual); 39 | } 40 | 41 | [Fact] 42 | public virtual void Can_Serialize_ImmutableMessageWithTwoConstructors() 43 | { 44 | var actual = new ImmutableMessages.ImmutableMessageWithDefaultParameters("John"); 45 | AssertEqual(actual); 46 | } 47 | 48 | protected T AssertAndReturn(T message) 49 | { 50 | var serializer = Sys.Serialization.FindSerializerFor(message); 51 | var serialized = serializer.ToBinary(message); 52 | var result = serializer.FromBinary(serialized, typeof(T)); 53 | return (T)result; 54 | } 55 | 56 | protected void AssertEqual(T message) 57 | { 58 | var deserialized = AssertAndReturn(message); 59 | Assert.Equal(message, deserialized); 60 | } 61 | } 62 | 63 | public static class ImmutableMessages 64 | { 65 | public class EmptyMessage { } 66 | 67 | public class EmptySingleton 68 | { 69 | public static EmptySingleton Instance { get; } = new EmptySingleton(); 70 | } 71 | 72 | public class MessageWithPublicSetters 73 | { 74 | public int Age { get; set; } 75 | 76 | public string Name { get; set; } 77 | 78 | private bool Equals(MessageWithPublicSetters other) 79 | { 80 | return String.Equals(Name, (string)other.Name) && Age == other.Age; 81 | } 82 | 83 | public override bool Equals(object obj) 84 | { 85 | if (ReferenceEquals(null, obj)) return false; 86 | if (ReferenceEquals(this, obj)) return true; 87 | return obj is MessageWithPublicSetters && Equals((MessageWithPublicSetters)obj); 88 | } 89 | 90 | public override int GetHashCode() 91 | { 92 | unchecked 93 | { 94 | return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Age; 95 | } 96 | } 97 | } 98 | 99 | public class ImmutableMessageWithPublicFields 100 | { 101 | public int Age; 102 | 103 | public string Name; 104 | 105 | private bool Equals(ImmutableMessageWithPublicFields other) 106 | { 107 | return string.Equals(Name, other.Name) && Age == other.Age; 108 | } 109 | 110 | public override bool Equals(object obj) 111 | { 112 | if (ReferenceEquals(null, obj)) return false; 113 | if (ReferenceEquals(this, obj)) return true; 114 | return obj is ImmutableMessageWithPublicFields && Equals((ImmutableMessageWithPublicFields)obj); 115 | } 116 | 117 | public override int GetHashCode() 118 | { 119 | unchecked 120 | { 121 | return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Age; 122 | } 123 | } 124 | } 125 | 126 | public class ImmutableMessage 127 | { 128 | public ImmutableMessage(string name, int age) 129 | { 130 | Age = age; 131 | Name = name; 132 | } 133 | 134 | public int Age { get; } 135 | 136 | public string Name { get; } 137 | 138 | private bool Equals(ImmutableMessage other) 139 | { 140 | return String.Equals(Name, (string)other.Name) && Age == other.Age; 141 | } 142 | 143 | public override bool Equals(object obj) 144 | { 145 | if (ReferenceEquals(null, obj)) return false; 146 | if (ReferenceEquals(this, obj)) return true; 147 | return obj is ImmutableMessage && Equals((ImmutableMessage)obj); 148 | } 149 | 150 | public override int GetHashCode() 151 | { 152 | unchecked 153 | { 154 | return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Age; 155 | } 156 | } 157 | } 158 | 159 | public class ImmutableMessageWithDefaultParameters 160 | { 161 | public ImmutableMessageWithDefaultParameters(string name, int age = 10) 162 | { 163 | Name = name; 164 | Age = age; 165 | } 166 | 167 | public int Age { get; } 168 | 169 | public string Name { get; } 170 | 171 | private bool Equals(ImmutableMessageWithDefaultParameters other) 172 | { 173 | return String.Equals(Name, (string)other.Name) && Age == other.Age; 174 | } 175 | 176 | public override bool Equals(object obj) 177 | { 178 | if (ReferenceEquals(null, obj)) return false; 179 | if (ReferenceEquals(this, obj)) return true; 180 | return obj is ImmutableMessageWithDefaultParameters && Equals((ImmutableMessageWithDefaultParameters)obj); 181 | } 182 | 183 | public override int GetHashCode() 184 | { 185 | unchecked 186 | { 187 | return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Age; 188 | } 189 | } 190 | } 191 | 192 | public class ImmutableMessageWithTwoConstructors 193 | { 194 | public ImmutableMessageWithTwoConstructors(int age, string name) 195 | { 196 | Age = age; 197 | Name = name; 198 | } 199 | 200 | public ImmutableMessageWithTwoConstructors(string name) 201 | { 202 | Name = name; 203 | Age = 4; 204 | } 205 | 206 | public int Age { get; } 207 | 208 | public string Name { get; } 209 | 210 | private bool Equals(ImmutableMessageWithTwoConstructors other) 211 | { 212 | return String.Equals(Name, (string)other.Name) && Age == other.Age; 213 | } 214 | 215 | public override bool Equals(object obj) 216 | { 217 | if (ReferenceEquals(null, obj)) return false; 218 | if (ReferenceEquals(this, obj)) return true; 219 | return obj is ImmutableMessageWithTwoConstructors && Equals((ImmutableMessageWithTwoConstructors)obj); 220 | } 221 | 222 | public override int GetHashCode() 223 | { 224 | unchecked 225 | { 226 | return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ Age; 227 | } 228 | } 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------