diff --git a/Akka.Persistence.Redis.sln b/Akka.Persistence.Redis.sln index 7d39e7f..8316fcb 100644 --- a/Akka.Persistence.Redis.sln +++ b/Akka.Persistence.Redis.sln @@ -25,6 +25,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.Redis", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.Redis.Tests", "src\Akka.Persistence.Redis.Tests\Akka.Persistence.Redis.Tests.csproj", "{59871C63-8D5D-4221-B504-203BB3500DBF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.Redis.Cluster.Tests", "src\Akka.Persistence.Redis.Cluster.Tests\Akka.Persistence.Redis.Cluster.Tests.csproj", "{18AF172B-8381-4AA2-976A-16626A402E00}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{DA31E2A8-AE1A-474D-843C-1B6848B921E2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.Redis.Benchmark.DockerTests", "src\benchmarks\Akka.Persistence.Redis.Benchmark.DockerTests\Akka.Persistence.Redis.Benchmark.DockerTests.csproj", "{BFBB3933-E8FC-4574-8D60-E6FA6BEABAE9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -47,6 +53,14 @@ Global {59871C63-8D5D-4221-B504-203BB3500DBF}.Debug|Any CPU.Build.0 = Debug|Any CPU {59871C63-8D5D-4221-B504-203BB3500DBF}.Release|Any CPU.ActiveCfg = Release|Any CPU {59871C63-8D5D-4221-B504-203BB3500DBF}.Release|Any CPU.Build.0 = Release|Any CPU + {18AF172B-8381-4AA2-976A-16626A402E00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18AF172B-8381-4AA2-976A-16626A402E00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18AF172B-8381-4AA2-976A-16626A402E00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18AF172B-8381-4AA2-976A-16626A402E00}.Release|Any CPU.Build.0 = Release|Any CPU + {BFBB3933-E8FC-4574-8D60-E6FA6BEABAE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BFBB3933-E8FC-4574-8D60-E6FA6BEABAE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BFBB3933-E8FC-4574-8D60-E6FA6BEABAE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BFBB3933-E8FC-4574-8D60-E6FA6BEABAE9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -54,6 +68,7 @@ Global GlobalSection(NestedProjects) = preSolution {6EEB8234-FAB9-4645-86EF-297240B3798A} = {1F453772-CA5E-4F41-A50E-75F3F59F72D6} {90084FC1-C04B-46BF-AA37-2ACD71E1AD4F} = {1F453772-CA5E-4F41-A50E-75F3F59F72D6} + {BFBB3933-E8FC-4574-8D60-E6FA6BEABAE9} = {DA31E2A8-AE1A-474D-843C-1B6848B921E2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6ADAF1F3-8019-4D3E-A8B0-2BF7CF7B439B} diff --git a/Akka.Persistence.Redis.sln.DotSettings b/Akka.Persistence.Redis.sln.DotSettings new file mode 100644 index 0000000..70fd111 --- /dev/null +++ b/Akka.Persistence.Redis.sln.DotSettings @@ -0,0 +1,6 @@ + + ----------------------------------------------------------------------- +<copyright file="$FILENAME$" company="Akka.NET Project"> + Copyright (C) 2013-2021 .NET Foundation <https://github.com/akkadotnet/akka.net> +</copyright> +----------------------------------------------------------------------- \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 1b8ff05..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at akka-contrib@petabridge.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ diff --git a/NuGet.Config b/NuGet.Config deleted file mode 100644 index 7d6b4c1..0000000 --- a/NuGet.Config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 1400d3a..77fd4c6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ # Akka.Persistence.Redis +![Akka.NET logo](docs/images/AkkaNetLogo.Normal.png) + [![NuGet Version](http://img.shields.io/nuget/v/Akka.Persistence.Redis.svg?style=flat)](https://www.nuget.org/packages/Akka.Persistence.Redis) Akka Persistence Redis Plugin is a plugin for `Akka persistence` that provides several components: - - a journal store ; - - a snapshot store ; - - a journal query interface implementation. + - a journal store and + - a snapshot store. + + > NOTE: in Akka.Persistence.Redis v1.4.16 we removed [Akka.Persistence.Query](https://getakka.net/articles/persistence/persistence-query.html) support. Please read more about that decision and comment here: https://github.com/akkadotnet/Akka.Persistence.Redis/issues/126 This plugin stores data in a [redis](https://redis.io) database and based on [Stackexchange.Redis](https://github.com/StackExchange/StackExchange.Redis) library. @@ -19,134 +22,138 @@ From `.NET CLI` dotnet add package Akka.Persistence.Redis ``` -## Journal plugin +## Journal To activate the journal plugin, add the following line to your HOCON config: ``` akka.persistence.journal.plugin = "akka.persistence.journal.redis" ``` This will run the journal with its default settings. The default settings can be changed with the configuration properties defined in your HOCON config: +``` +akka.persistence.journal.redis { + # qualified type name of the Redis persistence journal actor + class = "Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis" + + # connection string, as described here: https://github.com/StackExchange/StackExchange.Redis/blob/master/Docs/Configuration.md#basic-configuration-strings + configuration-string = "" + + # Redis journals key prefixes. Leave it for default or change it to appropriate value. WARNING: don't change it on production instances. + key-prefix = "" +} +``` + ### Configuration - `configuration-string` - connection string, as described here: https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Configuration.md#basic-configuration-strings -- `key-prefix` - Redis journals key prefixes. Leave it for default or change it to appropriate value. WARNING: don't change it on production instances. +- `key-prefix` - Redis journals key prefixes. Leave it for default or change it to customized value. WARNING: don't change this value after you've started persisting data in production. -## Snapshot config +## Snapshot Store To activate the snapshot plugin, add the following line to your HOCON config: ``` akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.redis" ``` This will run the snapshot-store with its default settings. The default settings can be changed with the configuration properties defined in your HOCON config: -### Configuration -- `configuration-string` - connection string, as described here: https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Configuration.md#basic-configuration-strings -- `key-prefix` - Redis journals key prefixes. Leave it for default or change it to appropriate value. WARNING: don't change it on production instances. - -## Persistence Query - -The plugin supports the following queries: - -### PersistenceIdsQuery and CurrentPersistenceIdsQuery -`PersistenceIds` and `CurrentPersistenceIds` are used for retrieving all persistenceIds of all persistent actors. -```C# -var readJournal = Sys.ReadJournalFor(RedisReadJournal.Identifier); - -Source willNotCompleteTheStream = readJournal.PersistenceIds(); -Source willCompleteTheStream = readJournal.CurrentPersistenceIds(); ``` -The returned event stream is unordered and you can expect different order for multiple executions of the query. - -When using the `PersistenceIds` query, the stream is not completed when it reaches the end of the currently used `persistenceIds`, but it continues to push new `persistenceIds` when new persistent actors are created. +akka.persistence.snapshot-store.redis { + # qualified type name of the Redis persistence journal actor + class = "Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis" -When using the `CurrentPersistenceIds` query, the stream is completed when the end of the current list of `persistenceIds` is reached, thus it is not a live query. + # connection string, as described here: https://github.com/StackExchange/StackExchange.Redis/blob/master/Docs/Configuration.md#basic-configuration-strings + configuration-string = "" -The stream is completed with failure if there is a failure in executing the query in the backend journal. + # Redis journals key prefixes. Leave it for default or change it to appropriate value. WARNING: don't change it on production instances. + key-prefix = "" +} +``` -### EventsByPersistenceIdQuery and CurrentEventsByPersistenceIdQuery +### Configuration +- `configuration-string` - connection string, as described here: https://github.com/StackExchange/StackExchange.Redis/blob/master/docs/Configuration.md#basic-configuration-strings +- `key-prefix` - Redis journals key prefixes. Leave it for default or change it to appropriate value. WARNING: don't change it on production instances. -`EventsByPersistenceId` and `CurrentEventsByPersistenceId` is used for retrieving events for a specific `PersistentActor` identified by `persistenceId`. -```C# -import akka.actor.ActorSystem -import akka.stream.{Materializer, ActorMaterializer} -import akka.stream.scaladsl.Source -import akka.persistence.query.{ PersistenceQuery, EventEnvelope } -import akka.persistence.jdbc.query.scaladsl.JdbcReadJournal +## Security and Access Control +You can secure the Redis server Akka.Persistence.Redis connects to by leveraging Redis ACL and requiring users to use AUTH to connect to the Redis server. -implicit val system: ActorSystem = ActorSystem() -implicit val mat: Materializer = ActorMaterializer()(system) -val readJournal: JdbcReadJournal = PersistenceQuery(system).readJournalFor[JdbcReadJournal](JdbcReadJournal.Identifier) +1. Redis ACL + You can use [Redis ACL](https://redis.io/topics/acl) to: + - Create users + - Set user passwords + - Limit the set of Redis commands a user can use + - Allow/disallow pub/sub channels + - Allow/disallow certain keys + - etc. -val willNotCompleteTheStream: Source[EventEnvelope, NotUsed] = readJournal.eventsByPersistenceId("some-persistence-id", 0L, Long.MaxValue) +2. Redis SSL/TLS + You can use [redis-cli](https://redis.io/topics/rediscli) to enable SSL/TLS feature in Redis. -val willCompleteTheStream: Source[EventEnvelope, NotUsed] = readJournal.currentEventsByPersistenceId("some-persistence-id", 0L, Long.MaxValue) +3. StackExchange.Redis Connection string + To connect to ACL enabled Redis server, you will need to set the user and password option in the [connection string](https://stackexchange.github.io/StackExchange.Redis/Configuration#basic-configuration-strings): + "myServer.net:6380,user=\,password=\" +All of these features are supported via `StackExchange.Redis`, which Akka.Persistence.Redis uses internally, and you only need to customize your `akka.persistence.journal.redis.configuration-string` and `akka.persistence.snapshot-store.redis.configuration-string` values to customize it. -var readJournal = Sys.ReadJournalFor(RedisReadJournal.Identifier); +### Enabling TLS +For instance, if you want to enable TLS on your Akka.Persistence.Redis instance: -Source willNotCompleteTheStream = queries.EventsByPersistenceId("some-persistence-id", 0L, long.MaxValue); -Source willCompleteTheStream = queries.CurrentEventsByPersistenceId("some-persistence-id", 0L, long.MaxValue); ``` -You can retrieve a subset of all events by specifying `fromSequenceNr` and `toSequenceNr` or use `0L` and `long.MaxValue` respectively to retrieve all events. Note that the corresponding sequence number of each event is provided in the `EventEnvelope`, which makes it possible to resume the stream at a later point from a given sequence number. - -The returned event stream is ordered by sequence number, i.e. the same order as the `PersistentActor` persisted the events. The same prefix of stream elements (in same order) are returned for multiple executions of the query, except for when events have been deleted. - -The stream is completed with failure if there is a failure in executing the query in the backend journal. - -### EventsByTag and CurrentEventsByTag - -`EventsByTag` and `CurrentEventsByTag` are used for retrieving events that were marked with a given tag, e.g. all domain events of an Aggregate Root type. -```C# -var readJournal = Sys.ReadJournalFor(RedisReadJournal.Identifier); - -Source willNotCompleteTheStream = queries.EventsByTag("apple", 0L); -Source willCompleteTheStream = queries.CurrentEventsByTag("apple", 0L); +akka.persistence.journal.redis.configuration-string = "contoso5.redis.cache.windows.net,ssl=true,password=..." ``` -### Tagging Events -To tag events you'll need to create an Event Adapter that will wrap the event in a akka.persistence.journal.Tagged class with the given tags. The Tagged class will instruct akka-persistence-jdbc to tag the event with the given set of tags. +Or if you need to connect to multiple redis instances in a cluster: -The persistence plugin will not store the Tagged class in the journal. It will strip the tags and payload from the Tagged class, and use the class only as an instruction to tag the event with the given tags and store the payload in the message field of the journal table. ``` -public class ColorTagger : IWriteEventAdapter -{ - public string Manifest(object evt) => string.Empty; - internal Tagged WithTag(object evt, string tag) => new Tagged(evt, ImmutableHashSet.Create(tag)); - - public object ToJournal(object evt) - { - switch (evt) - { - case string s when s.Contains("green"): - return WithTag(evt, "green"); - case string s when s.Contains("black"): - return WithTag(evt, "black"); - case string s when s.Contains("blue"): - return WithTag(evt, "blue"); - default: - return evt; - } - } -} +akka.persistence.journal.redis.configuration-string = "contoso5.redis.cache.windows.net, contoso4.redis.cache.windows.net,ssl=true,password=..." ``` -The EventAdapter must be registered by adding the following to the root of `application.conf` Please see the demo-akka-persistence-jdbc project for more information. + +### Enabling ACL +To connect to your redis instance with access control (ACL) support for Akka.Persistence.Redis, all you need to do is specify the user name and password in your connection string and this will restrict the `StackExchange.Redis` client used internally by Akka.Persistence.Redis to whatever permissions you specified in your cluster: + ``` -akka.persistence.journal.redis { - event-adapters { - color-tagger = "Akka.Persistence.Redis.Tests.Query.ColorTagger, Akka.Persistence.Redis.Tests" - } - event-adapter-bindings = { - "System.String" = color-tagger - } -} +akka.persistence.journal.redis.configuration-string = "contoso5.redis.cache.windows.net, contoso4.redis.cache.windows.net,user=akka-persistence,password=..." ``` -You can retrieve a subset of all events by specifying `offset`, or use `0L` to retrieve all events with a given tag. The `offset` corresponds to an ordered sequence number for the specific tag. Note that the corresponding offset of each event is provided in the `EventEnvelope`, which makes it possible to resume the stream at a later point from a given `offset`. -In addition to the `offset` the `EventEnvelope` also provides `persistenceId` and `sequenceNr` for each event. The `sequenceNr` is the sequence number for the persistent actor with the `persistenceId` that persisted the event. The `persistenceId` + `sequenceNr` is an unique identifier for the event. +#### Minimum command set +These are the minimum Redis commands that are needed by Akka.Persistence.Redis to work properly. + +| Redis Command | StackExchange.Redis Command | +|------------------|----------------------------------| +| MULTI | Transaction | +| EXEC | | +| DISCARD | | +| SET | String Set | +| SETNX | | +| SETEX | | +| GET | String Get | +| LLEN | List Length | +| LRANGE | List Range | +| RPUSH | List Right Push | +| RPUSHX | | +| LLEN | | +| ZADD | Sorted Set Add | +| ZREMRANGEBYSCORE | Delete Sorted Set by Score Range | +| ZREVRANGEBYSCORE | Get Sorted Set by Score Range | +| ZRANGEBYSCORE | | +| WITHSCORES | | +| LIMIT | | +| SSCAN | Scan | +| SMEMBERS | | +| PUBSUB | Pub/Sub | +| PING | | +| UNSUBSCRIBE | | +| SUBSCRIBE | | +| PSUBSCRIBE | | +| PUNSUBSCRIBE | | +| PUBLISH | Pub/Sub Publish | -The returned event stream contains only events that correspond to the given tag, and is ordered by the creation time of the events. The same stream elements (in same order) are returned for multiple executions of the same query. Deleted events are not deleted from the tagged event stream. ## Serialization -Akka Persistence provided serializers wrap the user payload in an envelope containing all persistence-relevant information. Redis Journal uses provided Protobuf serializers for the wrapper types (e.g. `IPersistentRepresentation`), then the payload will be serialized using the user configured serializer. By default, the payload will be serialized using JSON.NET serializer. This is fine for testing and initial phases of your development (while you’re still figuring out things and the data will not need to stay persisted forever). However, once you move to production you should really pick a different serializer for your payloads. +Akka Persistence provided serializers wrap the user payload in an envelope containing all persistence-relevant information. Redis Journal uses provided Protobuf serializers for the wrapper types (e.g. `IPersistentRepresentation`), then the payload will be serialized using the user configured serializer. + +The payload will be serialized [using Akka.NET's serialization bindings for your events and snapshot objects](https://getakka.net/articles/networking/serialization.html). By default, all `object`s that do not have a specified serializer will use Newtonsoft.Json polymorphic serialization (your CLR types <--> JSON.) + +This is fine for testing and initial phases of your development (while you’re still figuring out things and the data will not need to stay persisted forever). However, once you move to production you _should really pick a different serializer for your payloads_. + +We highly recommend creating schema-based serialization definitions using MsgPack, Google.Protobuf, or something similar and configuring serialization bindings for those in your configuration: https://getakka.net/articles/networking/serialization.html#usage Serialization of snapshots and payloads of Persistent messages is configurable with Akka’s Serialization infrastructure. For example, if an application wants to serialize @@ -163,7 +170,4 @@ akka.actor { "Akka.Persistence.Redis.Snapshot.SnapshotEntry, Akka.Persistence.Redis" = redis } } -``` - -## Maintainer -- [alexvaluyskiy](https://github.com/alexvaluyskiy) +``` \ No newline at end of file diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3f79b67..8b3373b 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,28 +1,40 @@ -#### 1.4.4 April 11th 2020 #### -- Bump Akka to version 1.4.4 -- Update build system to use Docker.DotNet - -#### 1.0.0-beta2 March 2nd 2020 #### -- Update Akka to version 1.4.1-RC1 -- Update build system - -#### 1.0.0-beta1 Sep 10 2017 #### -- Support for .NET Standard 1.6 -- Support for Persistence Query -- Use Google.Protobuf serialization both for journal and snapshots -- Updated Akka.Persistence to 1.3.1 -- StackExchange.Redis to 1.2.6 - -#### 0.2.5 Oct 16 2016 #### -- Updated Akka.Persistence to 1.1.2 -- Updated Json.Net to 9.0.1 -- StackExchange.Redis to 1.1.608 - -#### 0.2.0 Aug 12 2016 #### -- custom serializer for the events and snapshots -- use intermediate types JournalEntry and SnapshotEntry instead of default persistence types -- fixed sync call inside WriteMessagesAsync -- small optimizations and code refactoring - -#### 0.1.0 Jul 19 2016 #### -- First version of the package +#### 1.4.16 February 6th 2021 #### +This is a major update to the Akka.Persistence.Redis plugin. + +**Enabled Redis Cluster Support** +Akka.Persistence.Redis will now automatically detect whether or not you are running in clustered mode via your Redis connection string and will distribute journal entries and snapshots accordingly. + +All journal entries and all snapshots for a single entity will all reside inside the same Redis host cost - [using Redis' consistent hash distribution tagging](https://redis.io/topics/cluster-tutorial) scheme. + +**Significant Performance Improvements** +Akka.Persistence.Redis' write throughput was improved significantly in Akka.Persistence.Redis v1.4.16: + +| Test | Akka.Persistence.Redis v1.4.4 (msg/s) | current PR (msg/s) | +|-----------------|---------------------------------------|--------------------| +| Persist | 782 | 772 | +| PersistAll | 15019 | 20275 | +| PersistAsync | 9496 | 13131 | +| PersistAllAsync | 32765 | 44776 | +| PersistGroup10 | 611 | 6523 | +| PersistGroup100 | 8878 | 12533 | +| PersistGroup200 | 9598 | 12214 | +| PersistGroup25 | 9209 | 10819 | +| PersistGroup400 | 9209 | 11824 | +| PersistGroup50 | 9506 | 9704 | +| Recovering | 17374 | 20119 | +| Recovering8 | 36915 | 37290 | +| RecoveringFour | 22432 | 20884 | +| RecoveringTwo | 22209 | 21222 | + +These numbers were generated running a single Redis instance inside a Docker container on Docker for Windows - real-world values generated in cloud environments will likely be much higher. + +**Removed Akka.Persistence.Query Support** +In order to achieve support for clustering and improved write performance, we made the descision to drop Akka.Persistence.Query support from Akka.Persistence.Redis at this time - if you wish to learn more about our decision-making process or if you are affected by this change, please comment on this thread here: https://github.com/akkadotnet/Akka.Persistence.Redis/issues/126 + +**Other Changes** + +- Bump [Akka.NET to version 1.4.16](https://github.com/akkadotnet/akka.net/releases/tag/1.4.16) +- Modernized Akka.NET Serialization calls +- [Added benchmarks](https://github.com/akkadotnet/Akka.Persistence.Redis/pull/118) +- Upgraded to [StackExchange.Redis 2.2.11](https://github.com/StackExchange/StackExchange.Redis/blob/main/docs/ReleaseNotes.md) +- Improved documentation \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 359495e..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,57 +0,0 @@ -# configuration for "master" branch -- - branches: - only: - - master - image: Visual Studio 2017 - platform: Any CPU - configuration: Release - install: - - nuget install redis-64 -excludeversion - - redis-64\tools\redis-server.exe --service-install - - redis-64\tools\redis-server.exe --service-start - - '@ECHO Redis Started' - build_script: - - ps: ./build.ps1 Build - test_script: - - ps: ./build.ps1 RunTests - - ps: ./build.ps1 CreateNuget - artifacts: - - path: build/nuget/*.nupkg - name: nuget - deploy: - provider: NuGet - api_key: - secure: e7KuwYUTHtAEEVbxsIkRZR2052wcsY3DYJBGOpmGoIO2bHJyucVr7r50/TdXIQgw - skip_symbols: false - artifact: /.*\.nupkg/ - -# configuration for all branches starting from "dev" -- - branches: - only: - - dev - image: Visual Studio 2017 - platform: Any CPU - configuration: Release - install: - - nuget install redis-64 -excludeversion - - redis-64\tools\redis-server.exe --service-install - - redis-64\tools\redis-server.exe --service-start - - '@ECHO Redis Started' - build_script: - - ps: ./build.ps1 Build - test_script: - - ps: ./build.ps1 RunTests - - ps: ./build.ps1 CreateNuget - artifacts: - - path: build/nuget/*.nupkg - name: nuget - deploy: - provider: NuGet - server: https://www.myget.org/F/akkadotnet-contrib/api/v2/package - api_key: - secure: yoMgcchmDuXbvT90bgrUTOUNYgAId6QI0nofIOtnlfWzMjDEWfOqNqqTGPY+02M+ - skip_symbols: false - symbol_server: https://www.myget.org/F/akkadotnet-contrib/symbols/api/v2/package - artifact: /.*\.nupkg/ diff --git a/build-system/windows-release.yaml b/build-system/windows-release.yaml index 174cf74..9860c58 100644 --- a/build-system/windows-release.yaml +++ b/build-system/windows-release.yaml @@ -9,6 +9,7 @@ trigger: branches: include: - refs/tags/* +pr: none variables: - group: signingSecrets #create this group with SECRET variables `signingUsername` and `signingPassword` diff --git a/docs/images/AkkaNetLogo.Normal.png b/docs/images/AkkaNetLogo.Normal.png new file mode 100644 index 0000000..028060f Binary files /dev/null and b/docs/images/AkkaNetLogo.Normal.png differ diff --git a/src/Akka.Persistence.Redis.Cluster.Tests/Akka.Persistence.Redis.Cluster.Tests.csproj b/src/Akka.Persistence.Redis.Cluster.Tests/Akka.Persistence.Redis.Cluster.Tests.csproj new file mode 100644 index 0000000..50b6be7 --- /dev/null +++ b/src/Akka.Persistence.Redis.Cluster.Tests/Akka.Persistence.Redis.Cluster.Tests.csproj @@ -0,0 +1,47 @@ + + + + + Akka.Persistence.Redis.Cluster.Tests + $(NetFrameworkTestVersion);$(NetCoreTestVersion) + false + + + + $(NetFrameworkTestVersion);$(NetCoreTestVersion) + + + + + $(NetCoreTestVersion) + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + $(DefineConstants);RELEASE + + \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Cluster.Tests/DbUtils.cs b/src/Akka.Persistence.Redis.Cluster.Tests/DbUtils.cs new file mode 100644 index 0000000..4947965 --- /dev/null +++ b/src/Akka.Persistence.Redis.Cluster.Tests/DbUtils.cs @@ -0,0 +1,39 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System.Linq; +using StackExchange.Redis; + +namespace Akka.Persistence.Redis.Cluster.Test +{ + public static class DbUtils + { + public static string ConnectionString { get; private set; } + + public static void Initialize(RedisClusterFixture fixture) + { + ConnectionString = fixture.ConnectionString; + } + + public static void Initialize(string connectionString) + { + ConnectionString = connectionString; + } + + public static void Clean(int database = -1) + { + var connectionString = $"{ConnectionString},allowAdmin=true"; + + var redisConnection = ConnectionMultiplexer.Connect(connectionString); + foreach (var endPoint in redisConnection.GetEndPoints(false)) + { + var server = redisConnection.GetServer(endPoint); + if (!server.IsReplica) + server.FlushAllDatabases(); + } + } + } +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Cluster.Tests/Properties/AssemblyInfo.cs b/src/Akka.Persistence.Redis.Cluster.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f01250a --- /dev/null +++ b/src/Akka.Persistence.Redis.Cluster.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System.Reflection; +using System.Runtime.InteropServices; +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Cluster.Tests/RedisClusterFixture.cs b/src/Akka.Persistence.Redis.Cluster.Tests/RedisClusterFixture.cs new file mode 100644 index 0000000..87b2f6b --- /dev/null +++ b/src/Akka.Persistence.Redis.Cluster.Tests/RedisClusterFixture.cs @@ -0,0 +1,143 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Akka.Util; +using Docker.DotNet; +using Docker.DotNet.Models; +using Xunit; + +namespace Akka.Persistence.Redis.Cluster.Test +{ + [CollectionDefinition("RedisClusterSpec")] + public sealed class RedisSpecsFixture : ICollectionFixture + { + } + + public class RedisClusterFixture : IAsyncLifetime + { + protected readonly string RedisContainerName = $"redis-cluster-{Guid.NewGuid():N}"; + protected DockerClient Client; + + public RedisClusterFixture() + { + DockerClientConfiguration config; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + config = new DockerClientConfiguration(new Uri("unix://var/run/docker.sock")); + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + config = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine")); + else + throw new NotSupportedException($"Unsupported OS [{RuntimeInformation.OSDescription}]"); + + Client = config.CreateClient(); + } + + protected string ImageName => "grokzen/redis-cluster"; + protected string Tag => "latest"; + protected string RedisImageName => $"{ImageName}:{Tag}"; + + public string ConnectionString { get; private set; } + + public async Task InitializeAsync() + { + var images = await Client.Images.ListImagesAsync(new ImagesListParameters + { + Filters = new Dictionary> + { + {"reference", new Dictionary {{RedisImageName, true}}} + } + }); + if (images.Count == 0) + await Client.Images.CreateImageAsync( + new ImagesCreateParameters {FromImage = RedisImageName, Tag = "latest"}, null, + new Progress(message => + { + Console.WriteLine(!string.IsNullOrEmpty(message.ErrorMessage) + ? message.ErrorMessage + : $"{message.ID} {message.Status} {message.ProgressMessage}"); + })); + + var redisHostPort = ThreadLocalRandom.Current.Next(9000, 10000); + + // create the container + await Client.Containers.CreateContainerAsync(new CreateContainerParameters + { + Image = RedisImageName, + Name = RedisContainerName, + Tty = true, + Env = new List {"IP=0.0.0.0", $"INITIAL_PORT={redisHostPort}"}, + ExposedPorts = + new Dictionary + { + {$"{redisHostPort}/tcp", new EmptyStruct()}, + {$"{redisHostPort + 1}/tcp", new EmptyStruct()}, + {$"{redisHostPort + 2}/tcp", new EmptyStruct()}, + {$"{redisHostPort + 3}/tcp", new EmptyStruct()}, + {$"{redisHostPort + 4}/tcp", new EmptyStruct()}, + {$"{redisHostPort + 5}/tcp", new EmptyStruct()} + }, + HostConfig = new HostConfig + { + PortBindings = new Dictionary> + { + { + $"{redisHostPort}/tcp", + new List {new PortBinding {HostPort = $"{redisHostPort}"}} + }, + { + $"{redisHostPort + 1}/tcp", + new List {new PortBinding {HostPort = $"{redisHostPort + 1}"}} + }, + { + $"{redisHostPort + 2}/tcp", + new List {new PortBinding {HostPort = $"{redisHostPort + 2}"}} + }, + { + $"{redisHostPort + 3}/tcp", + new List {new PortBinding {HostPort = $"{redisHostPort + 3}"}} + }, + { + $"{redisHostPort + 4}/tcp", + new List {new PortBinding {HostPort = $"{redisHostPort + 4}"}} + }, + { + $"{redisHostPort + 5}/tcp", + new List {new PortBinding {HostPort = $"{redisHostPort + 5}"}} + } + } + } + }); + + // start the container + await Client.Containers.StartContainerAsync(RedisContainerName, new ContainerStartParameters()); + + // Provide a 10 second startup delay + await Task.Delay(TimeSpan.FromSeconds(10)); + + ConnectionString = $"127.0.0.1:{redisHostPort}"; + } + + public async Task DisposeAsync() + { + if (Client != null) + { + // Delay to make sure that all tests has completed cleanup. + await Task.Delay(TimeSpan.FromSeconds(5)); + + // Kill the container, we can't simply stop the container because Redis can hung indefinetly + // if we simply stop the container. + await Client.Containers.KillContainerAsync(RedisContainerName, new ContainerKillParameters()); + + await Client.Containers.RemoveContainerAsync(RedisContainerName, + new ContainerRemoveParameters {Force = true}); + Client.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Cluster.Tests/RedisJournalPerfSpec.cs b/src/Akka.Persistence.Redis.Cluster.Tests/RedisJournalPerfSpec.cs new file mode 100644 index 0000000..a6ad202 --- /dev/null +++ b/src/Akka.Persistence.Redis.Cluster.Tests/RedisJournalPerfSpec.cs @@ -0,0 +1,46 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System; +using Akka.Configuration; +using Akka.Persistence.TestKit.Performance; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.Persistence.Redis.Cluster.Test +{ + [Collection("RedisClusterSpec")] + public class RedisJournalPerfSpec : JournalPerfSpec + { + public static Config Config(RedisClusterFixture fixture) + { + DbUtils.Initialize(fixture); + + return ConfigurationFactory.ParseString($@" + akka.loglevel = INFO + akka.persistence.journal.plugin = ""akka.persistence.journal.redis"" + akka.persistence.journal.redis {{ + class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" + plugin-dispatcher = ""akka.actor.default-dispatcher"" + configuration-string = ""{DbUtils.ConnectionString}"" + }} + akka.test.single-expect-default = 3s") + .WithFallback(RedisPersistence.DefaultConfig()) + .WithFallback(Persistence.DefaultConfig()); + } + + public RedisJournalPerfSpec(ITestOutputHelper output, RedisClusterFixture fixture) + : base(Config(fixture), nameof(RedisJournalPerfSpec), output) + { + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + DbUtils.Clean(); + } + } +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Cluster.Tests/RedisJournalSpec.cs b/src/Akka.Persistence.Redis.Cluster.Tests/RedisJournalSpec.cs new file mode 100644 index 0000000..aa0fb3c --- /dev/null +++ b/src/Akka.Persistence.Redis.Cluster.Tests/RedisJournalSpec.cs @@ -0,0 +1,48 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using Akka.Configuration; +using Akka.Persistence.TCK.Journal; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.Persistence.Redis.Cluster.Test +{ + [Collection("RedisClusterSpec")] + public class RedisJournalSpec : JournalSpec + { + public static Config Config(RedisClusterFixture fixture) + { + DbUtils.Initialize(fixture); + + return ConfigurationFactory.ParseString($@" + akka.loglevel = INFO + akka.persistence.journal.plugin = ""akka.persistence.journal.redis"" + akka.persistence.journal.redis {{ + class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" + plugin-dispatcher = ""akka.actor.default-dispatcher"" + configuration-string = ""{DbUtils.ConnectionString}"" + }} + akka.test.single-expect-default = 3s") + .WithFallback(RedisPersistence.DefaultConfig()); + } + + public RedisJournalSpec(ITestOutputHelper output, RedisClusterFixture fixture) + : base(Config(fixture), nameof(RedisJournalSpec), output) + { + RedisPersistence.Get(Sys); + Initialize(); + } + + protected override bool SupportsRejectingNonSerializableObjects { get; } = false; + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + DbUtils.Clean(); + } + } +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Cluster.Tests/RedisSettingsSpec.cs b/src/Akka.Persistence.Redis.Cluster.Tests/RedisSettingsSpec.cs new file mode 100644 index 0000000..3913872 --- /dev/null +++ b/src/Akka.Persistence.Redis.Cluster.Tests/RedisSettingsSpec.cs @@ -0,0 +1,34 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using FluentAssertions; +using Xunit; + +namespace Akka.Persistence.Redis.Cluster.Test +{ + public class RedisSettingsSpec : Akka.TestKit.Xunit2.TestKit + { + [Fact] + public void Redis_JournalSettings_must_have_default_values() + { + var redisPersistence = RedisPersistence.Get(Sys); + + redisPersistence.JournalSettings.ConfigurationString.Should().Be(string.Empty); + redisPersistence.JournalSettings.Database.Should().Be(0); + redisPersistence.JournalSettings.KeyPrefix.Should().Be(string.Empty); + } + + [Fact] + public void Redis_SnapshotStoreSettingsSettings_must_have_default_values() + { + var redisPersistence = RedisPersistence.Get(Sys); + + redisPersistence.SnapshotStoreSettings.ConfigurationString.Should().Be(string.Empty); + redisPersistence.SnapshotStoreSettings.Database.Should().Be(0); + redisPersistence.SnapshotStoreSettings.KeyPrefix.Should().Be(string.Empty); + } + } +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Cluster.Tests/RedisSnapshotStoreSpec.cs b/src/Akka.Persistence.Redis.Cluster.Tests/RedisSnapshotStoreSpec.cs new file mode 100644 index 0000000..58776c4 --- /dev/null +++ b/src/Akka.Persistence.Redis.Cluster.Tests/RedisSnapshotStoreSpec.cs @@ -0,0 +1,60 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using Akka.Configuration; +using Akka.Persistence.TCK.Snapshot; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.Persistence.Redis.Cluster.Test +{ + [Collection("RedisClusterSpec")] + public class RedisSnapshotStoreSpec : SnapshotStoreSpec + { + public static Config Config(RedisClusterFixture fixture) + { + DbUtils.Initialize(fixture); + + return ConfigurationFactory.ParseString($@" + akka.test.single-expect-default = 3s + akka.persistence {{ + publish-plugin-commands = on + snapshot-store {{ + plugin = ""akka.persistence.snapshot-store.redis"" + redis {{ + class = ""Akka.Persistence.Redis.Snapshot.RedisSnapshotStore, Akka.Persistence.Redis"" + configuration-string = ""{fixture.ConnectionString}"" + plugin-dispatcher = ""akka.actor.default-dispatcher"" + }} + }} + }} + akka.actor {{ + serializers {{ + persistence-snapshot = ""Akka.Persistence.Redis.Serialization.PersistentSnapshotSerializer, Akka.Persistence.Redis"" + }} + serialization-bindings {{ + ""Akka.Persistence.SelectedSnapshot, Akka.Persistence"" = persistence-snapshot + }} + serialization-identifiers {{ + ""Akka.Persistence.Redis.Serialization.PersistentSnapshotSerializer, Akka.Persistence.Redis"" = 48 + }} + }}").WithFallback(RedisPersistence.DefaultConfig()); + } + + public RedisSnapshotStoreSpec(ITestOutputHelper output, RedisClusterFixture fixture) + : base(Config(fixture), nameof(RedisSnapshotStoreSpec), output) + { + RedisPersistence.Get(Sys); + Initialize(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + DbUtils.Clean(); + } + } +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Cluster.Tests/Serialization/RedisJournalSerializationSpec.cs b/src/Akka.Persistence.Redis.Cluster.Tests/Serialization/RedisJournalSerializationSpec.cs new file mode 100644 index 0000000..dc77fd7 --- /dev/null +++ b/src/Akka.Persistence.Redis.Cluster.Tests/Serialization/RedisJournalSerializationSpec.cs @@ -0,0 +1,57 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using Akka.Configuration; +using Akka.Persistence.TCK.Serialization; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.Persistence.Redis.Cluster.Test.Serialization +{ + [Collection("RedisClusterSpec")] + public class RedisJournalSerializationSpec : JournalSerializationSpec + { + public static Config Config(RedisClusterFixture fixture) + { + DbUtils.Initialize(fixture); + + return ConfigurationFactory.ParseString($@" + akka.loglevel = INFO + akka.persistence.journal.plugin = ""akka.persistence.journal.redis"" + akka.persistence.journal.redis {{ + event-adapters {{ + my-adapter = ""Akka.Persistence.TCK.Serialization.TestJournal+MyWriteAdapter, Akka.Persistence.TCK"" + }} + event-adapter-bindings = {{ + ""Akka.Persistence.TCK.Serialization.TestJournal+MyPayload3, Akka.Persistence.TCK"" = my-adapter + }} + class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" + plugin-dispatcher = ""akka.actor.default-dispatcher"" + configuration-string = ""{fixture.ConnectionString}"" + }} + akka.test.single-expect-default = 3s") + .WithFallback(RedisPersistence.DefaultConfig()); + } + + public RedisJournalSerializationSpec(ITestOutputHelper output, RedisClusterFixture fixture) + : base(Config(fixture), nameof(RedisJournalSerializationSpec), output) + { + } + + [Fact(Skip = "Unknown whether this test is correct or not, skip for now.")] + public override void Journal_should_serialize_Persistent_with_EventAdapter_manifest() + { + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + DbUtils.Clean(); + } + + protected override bool SupportsSerialization => false; + } +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Cluster.Tests/Serialization/RedisSnapshotStoreSerializationSpec.cs b/src/Akka.Persistence.Redis.Cluster.Tests/Serialization/RedisSnapshotStoreSerializationSpec.cs new file mode 100644 index 0000000..0264cf0 --- /dev/null +++ b/src/Akka.Persistence.Redis.Cluster.Tests/Serialization/RedisSnapshotStoreSerializationSpec.cs @@ -0,0 +1,55 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using Akka.Configuration; +using Akka.Persistence.TCK.Serialization; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.Persistence.Redis.Cluster.Test.Serialization +{ + [Collection("RedisClusterSpec")] + public class RedisSnapshotStoreSerializationSpec : SnapshotStoreSerializationSpec + { + public static Config Config(RedisClusterFixture fixture) + { + DbUtils.Initialize(fixture); + + return ConfigurationFactory.ParseString($@" + akka.loglevel = INFO + akka.persistence.snapshot-store.plugin = ""akka.persistence.snapshot-store.redis"" + akka.persistence.snapshot-store.redis {{ + class = ""Akka.Persistence.Redis.Snapshot.RedisSnapshotStore, Akka.Persistence.Redis"" + configuration-string = ""{fixture.ConnectionString}"" + plugin-dispatcher = ""akka.actor.default-dispatcher"" + }} + akka.actor {{ + serializers {{ + persistence-snapshot = ""Akka.Persistence.Redis.Serialization.PersistentSnapshotSerializer, Akka.Persistence.Redis"" + }} + serialization-bindings {{ + ""Akka.Persistence.SelectedSnapshot, Akka.Persistence"" = persistence-snapshot + }} + serialization-identifiers {{ + ""Akka.Persistence.Redis.Serialization.PersistentSnapshotSerializer, Akka.Persistence.Redis"" = 48 + }} + }} + akka.test.single-expect-default = 3s") + .WithFallback(RedisPersistence.DefaultConfig()); + } + + public RedisSnapshotStoreSerializationSpec(ITestOutputHelper output, RedisClusterFixture fixture) + : base(Config(fixture), nameof(RedisSnapshotStoreSerializationSpec), output) + { + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + DbUtils.Clean(); + } + } +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Tests/Akka.Persistence.Redis.Tests.csproj b/src/Akka.Persistence.Redis.Tests/Akka.Persistence.Redis.Tests.csproj index b5fd3ad..d44a4f8 100644 --- a/src/Akka.Persistence.Redis.Tests/Akka.Persistence.Redis.Tests.csproj +++ b/src/Akka.Persistence.Redis.Tests/Akka.Persistence.Redis.Tests.csproj @@ -4,6 +4,7 @@ Akka.Persistence.Redis.Tests $(NetFrameworkTestVersion);$(NetCoreTestVersion) + false @@ -23,10 +24,13 @@ - + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Akka.Persistence.Redis.Tests/DbUtils.cs b/src/Akka.Persistence.Redis.Tests/DbUtils.cs index f98e7b7..61d42af 100644 --- a/src/Akka.Persistence.Redis.Tests/DbUtils.cs +++ b/src/Akka.Persistence.Redis.Tests/DbUtils.cs @@ -1,9 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2009-2017 Lightbend Inc. -// Copyright (C) 2013-2017 Akka.NET project +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using System; using System.Linq; @@ -25,8 +24,12 @@ public static void Clean(int database) var connectionString = $"{ConnectionString},allowAdmin=true"; var redisConnection = ConnectionMultiplexer.Connect(connectionString); - var server = redisConnection.GetServer(redisConnection.GetEndPoints().First()); - server.FlushDatabase(database); + foreach (var endPoint in redisConnection.GetEndPoints()) + { + var server = redisConnection.GetServer(endPoint); + if (!server.IsReplica) + server.FlushAllDatabases(); + } } } -} +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Tests/Properties/AssemblyInfo.cs b/src/Akka.Persistence.Redis.Tests/Properties/AssemblyInfo.cs index b49fcb8..f01250a 100644 --- a/src/Akka.Persistence.Redis.Tests/Properties/AssemblyInfo.cs +++ b/src/Akka.Persistence.Redis.Tests/Properties/AssemblyInfo.cs @@ -1,4 +1,10 @@ -using System.Reflection; +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System.Reflection; using System.Runtime.InteropServices; using Xunit; diff --git a/src/Akka.Persistence.Redis.Tests/Query/RedisCurrentEventsByPersistenceIdSpec.cs b/src/Akka.Persistence.Redis.Tests/Query/RedisCurrentEventsByPersistenceIdSpec.cs deleted file mode 100644 index 325cc23..0000000 --- a/src/Akka.Persistence.Redis.Tests/Query/RedisCurrentEventsByPersistenceIdSpec.cs +++ /dev/null @@ -1,50 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using Akka.Configuration; -using Akka.Persistence.Query; -using Akka.Persistence.Redis.Query; -using Akka.Persistence.TCK.Query; -using Xunit; -using Xunit.Abstractions; - -namespace Akka.Persistence.Redis.Tests.Query -{ - [Collection("RedisSpec")] - public class RedisCurrentEventsByPersistenceIdSpec : CurrentEventsByPersistenceIdSpec - { - public const int Database = 1; - - public static Config Config(RedisFixture fixture, int id) - { - DbUtils.Initialize(fixture); - - return ConfigurationFactory.ParseString($@" - akka.loglevel = INFO - akka.persistence.journal.plugin = ""akka.persistence.journal.redis"" - akka.persistence.journal.redis {{ - class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" - plugin-dispatcher = ""akka.actor.default-dispatcher"" - configuration-string = ""{fixture.ConnectionString}"" - database = {id} - }} - akka.test.single-expect-default = 3s") - .WithFallback(RedisPersistence.DefaultConfig()); - } - - public RedisCurrentEventsByPersistenceIdSpec(ITestOutputHelper output, RedisFixture fixture) - : base(Config(fixture, Database), nameof(RedisCurrentEventsByPersistenceIdSpec), output) - { - ReadJournal = Sys.ReadJournalFor(RedisReadJournal.Identifier); - } - - protected override void Dispose(bool disposing) - { - DbUtils.Clean(Database); - base.Dispose(disposing); - } - } -} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Tests/Query/RedisCurrentEventsByTagSpec.cs b/src/Akka.Persistence.Redis.Tests/Query/RedisCurrentEventsByTagSpec.cs deleted file mode 100644 index b65a3a7..0000000 --- a/src/Akka.Persistence.Redis.Tests/Query/RedisCurrentEventsByTagSpec.cs +++ /dev/null @@ -1,56 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using Akka.Configuration; -using Akka.Persistence.Query; -using Akka.Persistence.Redis.Query; -using Akka.Persistence.TCK.Query; -using Xunit; -using Xunit.Abstractions; - -namespace Akka.Persistence.Redis.Tests.Query -{ - [Collection("RedisSpec")] - public sealed class RedisCurrentEventsByTagSpec : CurrentEventsByTagSpec - { - public const int Database = 1; - - public static Config Config(RedisFixture fixture, int id) - { - DbUtils.Initialize(fixture); - - return ConfigurationFactory.ParseString($@" - akka.loglevel = INFO - akka.persistence.journal.plugin = ""akka.persistence.journal.redis"" - akka.persistence.journal.redis {{ - event-adapters {{ - color-tagger = ""Akka.Persistence.TCK.Query.ColorFruitTagger, Akka.Persistence.TCK"" - }} - event-adapter-bindings = {{ - ""System.String"" = color-tagger - }} - class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" - plugin-dispatcher = ""akka.actor.default-dispatcher"" - configuration-string = ""{fixture.ConnectionString}"" - database = {id} - key-prefix = ""sbtech:"" - }} - akka.test.single-expect-default = 3s") - .WithFallback(RedisPersistence.DefaultConfig()); - } - - public RedisCurrentEventsByTagSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), nameof(RedisCurrentEventsByTagSpec), output) - { - ReadJournal = Sys.ReadJournalFor(RedisReadJournal.Identifier); - } - - protected override void Dispose(bool disposing) - { - DbUtils.Clean(Database); - base.Dispose(disposing); - } - } -} diff --git a/src/Akka.Persistence.Redis.Tests/Query/RedisCurrentPersistenceIdsSpec.cs b/src/Akka.Persistence.Redis.Tests/Query/RedisCurrentPersistenceIdsSpec.cs deleted file mode 100644 index edf4028..0000000 --- a/src/Akka.Persistence.Redis.Tests/Query/RedisCurrentPersistenceIdsSpec.cs +++ /dev/null @@ -1,54 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using Akka.Configuration; -using Akka.Persistence.Query; -using Akka.Persistence.Redis.Query; -using Akka.Persistence.TCK.Query; -using Xunit; -using Xunit.Abstractions; - -namespace Akka.Persistence.Redis.Tests.Query -{ - [Collection("RedisSpec")] - public sealed class RedisCurrentPersistenceIdsSpec : CurrentPersistenceIdsSpec - { - public const int Database = 1; - - public static Config Config(RedisFixture fixture, int id) - { - DbUtils.Initialize(fixture); - - return ConfigurationFactory.ParseString($@" - akka.loglevel = INFO - akka.persistence.journal.plugin = ""akka.persistence.journal.redis"" - akka.persistence.journal.redis {{ - class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" - plugin-dispatcher = ""akka.actor.default-dispatcher"" - configuration-string = ""{fixture.ConnectionString}"" - database = {id} - }} - akka.test.single-expect-default = 3s") - .WithFallback(RedisPersistence.DefaultConfig()); - } - - public RedisCurrentPersistenceIdsSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), nameof(RedisCurrentPersistenceIdsSpec), output) - { - ReadJournal = Sys.ReadJournalFor(RedisReadJournal.Identifier); - } - - [Fact(Skip = "Not implemented yet")] - public override void ReadJournal_query_CurrentPersistenceIds_should_not_see_new_events_after_complete() - { - } - - protected override void Dispose(bool disposing) - { - DbUtils.Clean(Database); - base.Dispose(disposing); - } - } -} diff --git a/src/Akka.Persistence.Redis.Tests/Query/RedisEventsByPersistenceIdSpec.cs b/src/Akka.Persistence.Redis.Tests/Query/RedisEventsByPersistenceIdSpec.cs deleted file mode 100644 index c59dd4f..0000000 --- a/src/Akka.Persistence.Redis.Tests/Query/RedisEventsByPersistenceIdSpec.cs +++ /dev/null @@ -1,49 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using Akka.Configuration; -using Akka.Persistence.Query; -using Akka.Persistence.Redis.Query; -using Akka.Persistence.TCK.Query; -using Xunit; -using Xunit.Abstractions; - -namespace Akka.Persistence.Redis.Tests.Query -{ - [Collection("RedisSpec")] - public class RedisEventsByPersistenceIdSpec : EventsByPersistenceIdSpec - { - public const int Database = 1; - - public static Config Config(RedisFixture fixture, int id) - { - DbUtils.Initialize(fixture); - - return ConfigurationFactory.ParseString($@" - akka.loglevel = INFO - akka.persistence.journal.plugin = ""akka.persistence.journal.redis"" - akka.persistence.journal.redis {{ - class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" - plugin-dispatcher = ""akka.actor.default-dispatcher"" - configuration-string = ""{fixture.ConnectionString}"" - database = {id} - }} - akka.test.single-expect-default = 3s") - .WithFallback(RedisPersistence.DefaultConfig()); - } - - public RedisEventsByPersistenceIdSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), nameof(RedisEventsByPersistenceIdSpec), output) - { - ReadJournal = Sys.ReadJournalFor(RedisReadJournal.Identifier); - } - - protected override void Dispose(bool disposing) - { - DbUtils.Clean(Database); - base.Dispose(disposing); - } - } -} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Tests/Query/RedisEventsByTagSpec.cs b/src/Akka.Persistence.Redis.Tests/Query/RedisEventsByTagSpec.cs deleted file mode 100644 index 6aade0e..0000000 --- a/src/Akka.Persistence.Redis.Tests/Query/RedisEventsByTagSpec.cs +++ /dev/null @@ -1,55 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using Akka.Configuration; -using Akka.Persistence.Query; -using Akka.Persistence.Redis.Query; -using Akka.Persistence.TCK.Query; -using Xunit; -using Xunit.Abstractions; - -namespace Akka.Persistence.Redis.Tests.Query -{ - [Collection("RedisSpec")] - public sealed class RedisEventsByTagSpec : EventsByTagSpec - { - public const int Database = 1; - - public static Config Config(RedisFixture fixture, int id) - { - DbUtils.Initialize(fixture); - - return ConfigurationFactory.ParseString($@" - akka.loglevel = INFO - akka.persistence.journal.plugin = ""akka.persistence.journal.redis"" - akka.persistence.journal.redis {{ - event-adapters {{ - color-tagger = ""Akka.Persistence.TCK.Query.ColorFruitTagger, Akka.Persistence.TCK"" - }} - event-adapter-bindings = {{ - ""System.String"" = color-tagger - }} - class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" - plugin-dispatcher = ""akka.actor.default-dispatcher"" - configuration-string = ""{fixture.ConnectionString}"" - database = {id} - }} - akka.test.single-expect-default = 3s") - .WithFallback(RedisPersistence.DefaultConfig()); - } - - public RedisEventsByTagSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), nameof(RedisEventsByTagSpec), output) - { - ReadJournal = Sys.ReadJournalFor(RedisReadJournal.Identifier); - } - - protected override void Dispose(bool disposing) - { - DbUtils.Clean(Database); - base.Dispose(disposing); - } - } -} diff --git a/src/Akka.Persistence.Redis.Tests/Query/RedisPersistenceIdsSpec.cs b/src/Akka.Persistence.Redis.Tests/Query/RedisPersistenceIdsSpec.cs deleted file mode 100644 index 7ca91d0..0000000 --- a/src/Akka.Persistence.Redis.Tests/Query/RedisPersistenceIdsSpec.cs +++ /dev/null @@ -1,71 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using Akka.Configuration; -using Akka.Persistence.Query; -using Akka.Persistence.Redis.Query; -using Akka.Persistence.TCK.Query; -using Xunit; -using Xunit.Abstractions; - -namespace Akka.Persistence.Redis.Tests.Query -{ - [Collection("RedisSpec")] - public sealed class RedisPersistenceIdsSpec : PersistenceIdsSpec - { - public const int Database = 1; - - public static Config Config(RedisFixture fixture, int id) - { - DbUtils.Initialize(fixture); - - return ConfigurationFactory.ParseString($@" - akka.loglevel = INFO - akka.persistence.journal.plugin = ""akka.persistence.journal.redis"" - akka.persistence.journal.redis {{ - class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" - plugin-dispatcher = ""akka.actor.default-dispatcher"" - configuration-string = ""{fixture.ConnectionString}"" - database = {id} - }} - akka.test.single-expect-default = 3s") - .WithFallback(RedisPersistence.DefaultConfig()); - } - - public RedisPersistenceIdsSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), nameof(RedisPersistenceIdsSpec), output) - { - ReadJournal = Sys.ReadJournalFor(RedisReadJournal.Identifier); - } - - [Fact(Skip = "Not implemented yet")] - public void ReadJournal_AllPersistenceIds_should_fail_the_stage_on_connection_error() - { - // setup redis - //var address = Sys.Settings.Config.GetString("akka.persistence.journal.redis.configuration-string"); - //var database = Sys.Settings.Config.GetInt("akka.persistence.journal.redis.database"); - - //var redis = ConnectionMultiplexer.Connect(address).GetDatabase(database); - - //var queries = ReadJournal.AsInstanceOf(); - - //Setup("a", 1); - - //var source = queries.AllPersistenceIds(); - //var probe = source.RunWith(this.SinkProbe(), Materializer); - - //// change type of value - //redis.StringSet("journal:persistenceIds", "1"); - - //probe.Within(TimeSpan.FromSeconds(10), () => probe.Request(1).ExpectError()); - } - - protected override void Dispose(bool disposing) - { - DbUtils.Clean(Database); - base.Dispose(disposing); - } - } -} diff --git a/src/Akka.Persistence.Redis.Tests/RedisClusterFixture.cs b/src/Akka.Persistence.Redis.Tests/RedisClusterFixture.cs new file mode 100644 index 0000000..4d70b1d --- /dev/null +++ b/src/Akka.Persistence.Redis.Tests/RedisClusterFixture.cs @@ -0,0 +1,107 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Akka.Util; +using Docker.DotNet; +using Docker.DotNet.Models; +using Xunit; + +namespace Akka.Persistence.Redis.Tests +{ + [CollectionDefinition("RedisClusterSpec")] + public sealed class RedisClusterSpecsFixture : ICollectionFixture + { + } + + public class RedisClusterFixture : IAsyncLifetime + { + protected readonly string RedisContainerName = $"redis-{Guid.NewGuid():N}"; + protected DockerClient Client; + + public RedisClusterFixture() + { + DockerClientConfiguration config; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + config = new DockerClientConfiguration(new Uri("unix://var/run/docker.sock")); + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + config = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine")); + else + throw new NotSupportedException($"Unsupported OS [{RuntimeInformation.OSDescription}]"); + + Client = config.CreateClient(); + } + + protected string RedisImageName => "grokzen/redis-cluster"; + + public string ConnectionString { get; private set; } + + public async Task InitializeAsync() + { + var images = await Client.Images.ListImagesAsync(new ImagesListParameters {MatchName = RedisImageName}); + if (images.Count == 0) + await Client.Images.CreateImageAsync( + new ImagesCreateParameters {FromImage = RedisImageName, Tag = "latest"}, null, + new Progress(message => + { + Console.WriteLine(!string.IsNullOrEmpty(message.ErrorMessage) + ? message.ErrorMessage + : $"{message.ID} {message.Status} {message.ProgressMessage}"); + })); + + var redisHostPort = ThreadLocalRandom.Current.Next(9000, 10000); + + // create the container + await Client.Containers.CreateContainerAsync(new CreateContainerParameters + { + Image = RedisImageName, + Name = RedisContainerName, + Tty = true, + ExposedPorts = new Dictionary {{"6379/tcp", new EmptyStruct()}}, + HostConfig = new HostConfig + { + PortBindings = new Dictionary> + { + { + "6379/tcp", new List {new PortBinding {HostPort = $"{redisHostPort}"}} + } + } + } + }); + + + // start the container + await Client.Containers.StartContainerAsync(RedisContainerName, new ContainerStartParameters()); + + // Provide a 30 second startup delay + await Task.Delay(TimeSpan.FromSeconds(10)); + + ConnectionString = $"localhost:{redisHostPort}"; + } + + public async Task DisposeAsync() + { + if (Client != null) + { + // Delay to make sure that all tests has completed cleanup. + await Task.Delay(TimeSpan.FromSeconds(5)); + + // Kill the container, we can't simply stop the container because Redis can hung indefinetly + // if we simply stop the container. + await Client.Containers.KillContainerAsync(RedisContainerName, new ContainerKillParameters()); + + await Client.Containers.RemoveContainerAsync(RedisContainerName, + new ContainerRemoveParameters {Force = true}); + Client.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Tests/RedisFixture.cs b/src/Akka.Persistence.Redis.Tests/RedisFixture.cs index 6ea12ec..48ae7ee 100644 --- a/src/Akka.Persistence.Redis.Tests/RedisFixture.cs +++ b/src/Akka.Persistence.Redis.Tests/RedisFixture.cs @@ -1,4 +1,10 @@ -using System; +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System; using System.Collections.Generic; using System.Data.Common; using System.Runtime.InteropServices; @@ -33,16 +39,24 @@ public RedisFixture() Client = config.CreateClient(); } - protected string RedisImageName => "redis"; + protected string ImageName => "redis"; + protected string Tag => "latest"; + protected string RedisImageName => $"{ImageName}:{Tag}"; public string ConnectionString { get; private set; } public async Task InitializeAsync() { - var images = await Client.Images.ListImagesAsync(new ImagesListParameters { MatchName = RedisImageName }); + var images = await Client.Images.ListImagesAsync(new ImagesListParameters + { + Filters = new Dictionary> + { + {"reference", new Dictionary {{RedisImageName, true}}} + } + }); if (images.Count == 0) await Client.Images.CreateImageAsync( - new ImagesCreateParameters { FromImage = RedisImageName, Tag = "latest" }, null, + new ImagesCreateParameters {FromImage = ImageName, Tag = Tag}, null, new Progress(message => { Console.WriteLine(!string.IsNullOrEmpty(message.ErrorMessage) @@ -58,29 +72,19 @@ await Client.Containers.CreateContainerAsync(new CreateContainerParameters Image = RedisImageName, Name = RedisContainerName, Tty = true, - ExposedPorts = new Dictionary - { - {"6379/tcp", new EmptyStruct()} - }, + ExposedPorts = new Dictionary {{"6379/tcp", new EmptyStruct()}}, HostConfig = new HostConfig { PortBindings = new Dictionary> { { - "6379/tcp", - new List - { - new PortBinding - { - HostPort = $"{redisHostPort}" - } - } + "6379/tcp", new List {new PortBinding {HostPort = $"{redisHostPort}"}} } } } }); - + // start the container await Client.Containers.StartContainerAsync(RedisContainerName, new ContainerStartParameters()); @@ -102,9 +106,9 @@ public async Task DisposeAsync() await Client.Containers.KillContainerAsync(RedisContainerName, new ContainerKillParameters()); await Client.Containers.RemoveContainerAsync(RedisContainerName, - new ContainerRemoveParameters { Force = true }); + new ContainerRemoveParameters {Force = true}); Client.Dispose(); } } } -} +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Tests/RedisJournalPerfSpec.cs b/src/Akka.Persistence.Redis.Tests/RedisJournalPerfSpec.cs index 49e531c..0cb33ec 100644 --- a/src/Akka.Persistence.Redis.Tests/RedisJournalPerfSpec.cs +++ b/src/Akka.Persistence.Redis.Tests/RedisJournalPerfSpec.cs @@ -1,11 +1,11 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2017 Akka.NET Contrib +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- +using System; using Akka.Configuration; -using Akka.Persistence.Redis.Query; using Akka.Persistence.TestKit.Performance; using Xunit; using Xunit.Abstractions; @@ -31,11 +31,12 @@ class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" database = {id} }} akka.test.single-expect-default = 3s") - .WithFallback(RedisPersistence.DefaultConfig()) - .WithFallback(Persistence.DefaultConfig()); + .WithFallback(RedisPersistence.DefaultConfig()) + .WithFallback(Persistence.DefaultConfig()); } - public RedisJournalPerfSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), nameof(RedisJournalPerfSpec), output) + public RedisJournalPerfSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), + nameof(RedisJournalPerfSpec), output) { } @@ -45,4 +46,4 @@ protected override void Dispose(bool disposing) DbUtils.Clean(Database); } } -} +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Tests/RedisJournalSpec.cs b/src/Akka.Persistence.Redis.Tests/RedisJournalSpec.cs index 6fa808a..16687f2 100644 --- a/src/Akka.Persistence.Redis.Tests/RedisJournalSpec.cs +++ b/src/Akka.Persistence.Redis.Tests/RedisJournalSpec.cs @@ -1,11 +1,10 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2017 Akka.NET Contrib +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using Akka.Configuration; -using Akka.Persistence.Redis.Query; using Akka.Persistence.TCK.Journal; using Xunit; using Xunit.Abstractions; @@ -31,10 +30,11 @@ class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" database = {id} }} akka.test.single-expect-default = 3s") - .WithFallback(RedisPersistence.DefaultConfig()); + .WithFallback(RedisPersistence.DefaultConfig()); } - public RedisJournalSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), nameof(RedisJournalSpec), output) + public RedisJournalSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), + nameof(RedisJournalSpec), output) { RedisPersistence.Get(Sys); Initialize(); diff --git a/src/Akka.Persistence.Redis.Tests/RedisSettingsSpec.cs b/src/Akka.Persistence.Redis.Tests/RedisSettingsSpec.cs index 4b4c7f9..6aa4093 100644 --- a/src/Akka.Persistence.Redis.Tests/RedisSettingsSpec.cs +++ b/src/Akka.Persistence.Redis.Tests/RedisSettingsSpec.cs @@ -1,8 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2017 Akka.NET Contrib +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using FluentAssertions; using Xunit; @@ -31,4 +31,4 @@ public void Redis_SnapshotStoreSettingsSettings_must_have_default_values() redisPersistence.SnapshotStoreSettings.KeyPrefix.Should().Be(string.Empty); } } -} +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Tests/RedisSnapshotStoreSpec.cs b/src/Akka.Persistence.Redis.Tests/RedisSnapshotStoreSpec.cs index 1092fca..71958b8 100644 --- a/src/Akka.Persistence.Redis.Tests/RedisSnapshotStoreSpec.cs +++ b/src/Akka.Persistence.Redis.Tests/RedisSnapshotStoreSpec.cs @@ -1,11 +1,10 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2017 Akka.NET Contrib +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using Akka.Configuration; -using Akka.Persistence.Redis.Query; using Akka.Persistence.TCK.Snapshot; using Xunit; using Xunit.Abstractions; diff --git a/src/Akka.Persistence.Redis.Tests/Serialization/RedisJournalSerializationSpec.cs b/src/Akka.Persistence.Redis.Tests/Serialization/RedisJournalSerializationSpec.cs index 7a8e09c..ccd5c12 100644 --- a/src/Akka.Persistence.Redis.Tests/Serialization/RedisJournalSerializationSpec.cs +++ b/src/Akka.Persistence.Redis.Tests/Serialization/RedisJournalSerializationSpec.cs @@ -1,11 +1,10 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2017 Akka.NET Contrib +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using Akka.Configuration; -using Akka.Persistence.Redis.Query; using Akka.Persistence.TCK.Serialization; using Xunit; using Xunit.Abstractions; @@ -37,10 +36,11 @@ class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" database = {id} }} akka.test.single-expect-default = 3s") - .WithFallback(RedisPersistence.DefaultConfig()); + .WithFallback(RedisPersistence.DefaultConfig()); } - public RedisJournalSerializationSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), nameof(RedisJournalSerializationSpec), output) + public RedisJournalSerializationSpec(ITestOutputHelper output, RedisFixture fixture) : base( + Config(fixture, Database), nameof(RedisJournalSerializationSpec), output) { } @@ -55,6 +55,6 @@ protected override void Dispose(bool disposing) DbUtils.Clean(Database); } - protected override bool SupportsSerialization => false; + protected override bool SupportsSerialization => true; } } \ No newline at end of file diff --git a/src/Akka.Persistence.Redis.Tests/Serialization/RedisSnapshotStoreSerializationSpec.cs b/src/Akka.Persistence.Redis.Tests/Serialization/RedisSnapshotStoreSerializationSpec.cs index f8e8213..603927a 100644 --- a/src/Akka.Persistence.Redis.Tests/Serialization/RedisSnapshotStoreSerializationSpec.cs +++ b/src/Akka.Persistence.Redis.Tests/Serialization/RedisSnapshotStoreSerializationSpec.cs @@ -1,11 +1,10 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2017 Akka.NET Contrib +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using Akka.Configuration; -using Akka.Persistence.Redis.Query; using Akka.Persistence.TCK.Serialization; using Xunit; using Xunit.Abstractions; @@ -42,10 +41,11 @@ class = ""Akka.Persistence.Redis.Snapshot.RedisSnapshotStore, Akka.Persistence.R }} }} akka.test.single-expect-default = 3s") - .WithFallback(RedisPersistence.DefaultConfig()); + .WithFallback(RedisPersistence.DefaultConfig()); } - public RedisSnapshotStoreSerializationSpec(ITestOutputHelper output, RedisFixture fixture) : base(Config(fixture, Database), nameof(RedisSnapshotStoreSerializationSpec), output) + public RedisSnapshotStoreSerializationSpec(ITestOutputHelper output, RedisFixture fixture) : base( + Config(fixture, Database), nameof(RedisSnapshotStoreSerializationSpec), output) { } diff --git a/src/Akka.Persistence.Redis/Akka.Persistence.Redis.csproj b/src/Akka.Persistence.Redis/Akka.Persistence.Redis.csproj index e5a47e8..72b41d1 100644 --- a/src/Akka.Persistence.Redis/Akka.Persistence.Redis.csproj +++ b/src/Akka.Persistence.Redis/Akka.Persistence.Redis.csproj @@ -22,6 +22,5 @@ - diff --git a/src/Akka.Persistence.Redis/Journal/RedisJournal.cs b/src/Akka.Persistence.Redis/Journal/RedisJournal.cs index 0c0ef30..70b02a4 100644 --- a/src/Akka.Persistence.Redis/Journal/RedisJournal.cs +++ b/src/Akka.Persistence.Redis/Journal/RedisJournal.cs @@ -1,8 +1,8 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using System; using System.Collections.Generic; @@ -24,6 +24,7 @@ public class RedisJournal : AsyncWriteJournal private ActorSystem _system; public IDatabase Database => _database.Value; + public bool IsClustered { get; private set; } public RedisJournal() { @@ -38,14 +39,16 @@ protected override void PreStart() _database = new Lazy(() => { var redisConnection = ConnectionMultiplexer.Connect(_settings.ConfigurationString); + IsClustered = redisConnection.IsClustered(); return redisConnection.GetDatabase(_settings.Database); }); } public override async Task ReadHighestSequenceNrAsync(string persistenceId, long fromSequenceNr) { - var highestSequenceNr = await Database.StringGetAsync(_journalHelper.GetHighestSequenceNrKey(persistenceId)); - return highestSequenceNr.IsNull ? 0L : (long)highestSequenceNr; + var highestSequenceNr = + await Database.StringGetAsync(_journalHelper.GetHighestSequenceNrKey(persistenceId, IsClustered)); + return highestSequenceNr.IsNull ? 0L : (long) highestSequenceNr; } public override async Task ReplayMessagesAsync( @@ -56,27 +59,24 @@ public override async Task ReplayMessagesAsync( long max, Action recoveryCallback) { - RedisValue[] journals = await Database.SortedSetRangeByScoreAsync(_journalHelper.GetJournalKey(persistenceId), fromSequenceNr, toSequenceNr, skip: 0L, take: max); + var journals = await Database.SortedSetRangeByScoreAsync( + _journalHelper.GetJournalKey(persistenceId, IsClustered), fromSequenceNr, toSequenceNr, skip: 0L, + take: max); - foreach (var journal in journals) - { - recoveryCallback(_journalHelper.PersistentFromBytes(journal)); - } + foreach (var journal in journals) recoveryCallback(_journalHelper.PersistentFromBytes(journal)); } protected override async Task DeleteMessagesToAsync(string persistenceId, long toSequenceNr) { - await Database.SortedSetRemoveRangeByScoreAsync(_journalHelper.GetJournalKey(persistenceId), -1, toSequenceNr); + await Database.SortedSetRemoveRangeByScoreAsync(_journalHelper.GetJournalKey(persistenceId, IsClustered), + -1, toSequenceNr); } protected override async Task> WriteMessagesAsync(IEnumerable messages) { var writeTasks = messages.Select(WriteBatchAsync).ToList(); - foreach (var writeTask in writeTasks) - { - await writeTask; - } + foreach (var writeTask in writeTasks) await writeTask; return writeTasks .Select(t => t.IsFaulted ? TryUnwrapException(t.Exception) : null) @@ -86,7 +86,11 @@ protected override async Task> WriteMessagesAsync(IEnu #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed private async Task WriteBatchAsync(AtomicWrite aw) { - var transaction = Database.CreateTransaction(); + var eventList = new List(); + var persistenceIdPublishList = new List<(string, long)>(); + var eventIdList = new List(); + var eventTagList = new List<(string, string)>(); + var tagList = new List(); var payloads = aw.Payload.AsInstanceOf>(); foreach (var payload in payloads) @@ -94,23 +98,87 @@ private async Task WriteBatchAsync(AtomicWrite aw) var (bytes, tags) = Extract(payload); // save the payload - transaction.SortedSetAddAsync(_journalHelper.GetJournalKey(payload.PersistenceId), bytes, payload.SequenceNr); + eventList.Add(new SortedSetEntry(bytes, payload.SequenceNr)); + //transaction.SortedSetAddAsync(_journalHelper.GetJournalKey(payload.PersistenceId, IsClustered), bytes, payload.SequenceNr); - // notify about a new event being appended for this persistence id - transaction.PublishAsync(_journalHelper.GetJournalChannel(payload.PersistenceId), payload.SequenceNr); + persistenceIdPublishList.Add((_journalHelper.GetJournalChannel(payload.PersistenceId, IsClustered), + payload.SequenceNr)); + + var journalEventIdentifier = $"{payload.SequenceNr}:{payload.PersistenceId}"; + eventIdList.Add(journalEventIdentifier); - // save tags foreach (var tag in tags) { - transaction.ListRightPushAsync(_journalHelper.GetTagKey(tag), $"{payload.SequenceNr}:{payload.PersistenceId}"); - transaction.PublishAsync(_journalHelper.GetTagsChannel(), tag); + eventTagList.Add((_journalHelper.GetTagKey(tag, IsClustered), journalEventIdentifier)); + tagList.Add(tag); } } + var transaction = Database.CreateTransaction(); + transaction.SortedSetAddAsync(_journalHelper.GetJournalKey(aw.PersistenceId, IsClustered), + eventList.ToArray()); + // set highest sequence number key - transaction.StringSetAsync(_journalHelper.GetHighestSequenceNrKey(aw.PersistenceId), aw.HighestSequenceNr); + transaction.StringSetAsync(_journalHelper.GetHighestSequenceNrKey(aw.PersistenceId, IsClustered), + aw.HighestSequenceNr); + if (!await transaction.ExecuteAsync()) + throw new Exception( + $"{nameof(WriteMessagesAsync)}: failed to write {nameof(IPersistentRepresentation)} to redis"); + + #region Query support + + /* + //save events sequenceNr and persistenceId so that we can read all events + //with it starting from a given sequenceNr + var key = _journalHelper.GetEventsKey(); + transaction = Database.CreateTransaction(); + foreach (var evt in eventIdList) + { + transaction.ListRightPushAsync(key, evt); + } + if (!await transaction.ExecuteAsync()) + throw new Exception($"{nameof(WriteMessagesAsync)}: failed to write {nameof(IPersistentRepresentation)} to redis"); + + // save tags + transaction = Database.CreateTransaction(); + foreach (var (k, e) in eventTagList) + { + transaction.ListRightPushAsync(k, e); + } + if (!await transaction.ExecuteAsync()) + throw new Exception($"{nameof(WriteMessagesAsync)}: failed to write {nameof(IPersistentRepresentation)} to redis"); + + // notify about a new event being appended for this persistence id + transaction = Database.CreateTransaction(); + foreach (var (id, nr) in persistenceIdPublishList) + { + transaction.PublishAsync(id, nr); + } + if (!await transaction.ExecuteAsync()) + throw new Exception($"{nameof(WriteMessagesAsync)}: failed to write {nameof(IPersistentRepresentation)} to redis"); + + // notify about all event + key = _journalHelper.GetEventsChannel(); + transaction = Database.CreateTransaction(); + foreach (var evt in eventIdList) + { + transaction.PublishAsync(key, evt); + } + if (!await transaction.ExecuteAsync()) + throw new Exception($"{nameof(WriteMessagesAsync)}: failed to write {nameof(IPersistentRepresentation)} to redis"); + + // publish tags + key = _journalHelper.GetTagsChannel(); + transaction = Database.CreateTransaction(); + foreach (var tag in tagList) + { + transaction.PublishAsync(key, tag); + } + if (!await transaction.ExecuteAsync()) + throw new Exception($"{nameof(WriteMessagesAsync)}: failed to write {nameof(IPersistentRepresentation)} to redis"); // add persistenceId + transaction = Database.CreateTransaction(); transaction.SetAddAsync(_journalHelper.GetIdentifiersKey(), aw.PersistenceId).ContinueWith(task => { if (task.Result) @@ -119,24 +187,19 @@ private async Task WriteBatchAsync(AtomicWrite aw) Database.Publish(_journalHelper.GetIdentifiersChannel(), aw.PersistenceId); } }); - if (!await transaction.ExecuteAsync()) - { - throw new Exception($"{nameof(WriteMessagesAsync)}: failed to write {typeof(IPersistentRepresentation).Name} to redis"); - } + throw new Exception($"{nameof(WriteMessagesAsync)}: failed to write {nameof(IPersistentRepresentation)} to redis"); + */ + + #endregion } #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - private (byte[], IImmutableSet) Extract(IPersistentRepresentation pr) { if (pr.Payload is Tagged tag) - { return (_journalHelper.PersistentToBytes(pr.WithPayload(tag.Payload)), tag.Tags); - } else - { return (_journalHelper.PersistentToBytes(pr), ImmutableHashSet.Empty); - } } } } \ No newline at end of file diff --git a/src/Akka.Persistence.Redis/JournalHelper.cs b/src/Akka.Persistence.Redis/JournalHelper.cs index 27a3bb4..1459844 100644 --- a/src/Akka.Persistence.Redis/JournalHelper.cs +++ b/src/Akka.Persistence.Redis/JournalHelper.cs @@ -1,8 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2017 Akka.NET Contrib +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using Akka.Actor; @@ -11,10 +11,14 @@ namespace Akka.Persistence.Redis internal class JournalHelper { private readonly ActorSystem _system; + private readonly Akka.Serialization.Serialization _serialization; + private readonly Akka.Serialization.Serializer _serializer; public JournalHelper(ActorSystem system, string keyPrefix) { _system = system; + _serialization = system.Serialization; + _serializer = _serialization.FindSerializerForType(typeof(Persistent)); KeyPrefix = keyPrefix; } @@ -22,22 +26,67 @@ public JournalHelper(ActorSystem system, string keyPrefix) public byte[] PersistentToBytes(IPersistentRepresentation message) { - var serializer = _system.Serialization.FindSerializerForType(typeof(IPersistentRepresentation)); - return serializer.ToBinary(message); + return _serialization.Serialize(message); } public IPersistentRepresentation PersistentFromBytes(byte[] bytes) { - var serializer = _system.Serialization.FindSerializerForType(typeof(IPersistentRepresentation)); - return serializer.FromBinary(bytes); + var p = (IPersistentRepresentation) _serialization.Deserialize(bytes, _serializer.Identifier, + typeof(Persistent)); + return p; } - public string GetIdentifiersKey() => $"{KeyPrefix}journal:persistenceIds"; - public string GetHighestSequenceNrKey(string persistenceId) => $"{KeyPrefix}journal:persisted:{persistenceId}:highestSequenceNr"; - public string GetJournalKey(string persistenceId) => $"{KeyPrefix}journal:persisted:{persistenceId}"; - public string GetJournalChannel(string persistenceId) => $"{KeyPrefix}journal:channel:persisted:{persistenceId}"; - public string GetTagKey(string tag) => $"{KeyPrefix}journal:tag:{tag}"; - public string GetTagsChannel() => $"{KeyPrefix}journal:channel:tags"; - public string GetIdentifiersChannel() => $"{KeyPrefix}journal:channel:ids"; + public string GetIdentifiersKey() + { + return $"{KeyPrefix}journal:persistenceIds"; + } + + public string GetTagsChannel() + { + return $"{KeyPrefix}journal:channel:tags"; + } + + public string GetEventsChannel() + { + return $"{KeyPrefix}journal:channel:events"; + } + + public string GetEventsKey() + { + return $"{KeyPrefix}journal:events"; + } + + public string GetHighestSequenceNrKey(string persistenceId, bool withHashTag) + { + return withHashTag + ? $"{{__{persistenceId}}}.{KeyPrefix}journal:persisted:{persistenceId}:highestSequenceNr" + : $"{KeyPrefix}journal:persisted:{persistenceId}:highestSequenceNr"; + } + + public string GetJournalKey(string persistenceId, bool withHasTag) + { + return withHasTag + ? $"{{__{persistenceId}}}.{KeyPrefix}journal:persisted:{persistenceId}" + : $"{KeyPrefix}journal:persisted:{persistenceId}"; + } + + public string GetJournalChannel(string persistenceId, bool withHasTag) + { + return withHasTag + ? $"{{__journal_channel}}.{KeyPrefix}journal:channel:persisted:{persistenceId}" + : $"{KeyPrefix}journal:channel:persisted:{persistenceId}"; + } + + public string GetTagKey(string tag, bool withHashTag) + { + return withHashTag + ? $"{{__tags}}.{KeyPrefix}journal:tag:{tag}" + : $"{KeyPrefix}journal:tag:{tag}"; + } + + public string GetIdentifiersChannel() + { + return $"{KeyPrefix}journal:channel:ids"; + } } -} +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis/Properties/AssemblyInfo.cs b/src/Akka.Persistence.Redis/Properties/AssemblyInfo.cs index 26138b8..b43855d 100644 --- a/src/Akka.Persistence.Redis/Properties/AssemblyInfo.cs +++ b/src/Akka.Persistence.Redis/Properties/AssemblyInfo.cs @@ -1,4 +1,10 @@ -using System.Reflection; +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; diff --git a/src/Akka.Persistence.Redis/Query/RedisReadJournal.cs b/src/Akka.Persistence.Redis/Query/RedisReadJournal.cs deleted file mode 100644 index d8c0b3c..0000000 --- a/src/Akka.Persistence.Redis/Query/RedisReadJournal.cs +++ /dev/null @@ -1,117 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using Akka.Actor; -using Akka.Configuration; -using Akka.Persistence.Query; -using Akka.Streams.Dsl; -using StackExchange.Redis; -using System; -using Akka.Persistence.Redis.Query.Stages; -using Akka.Streams; - -namespace Akka.Persistence.Redis.Query -{ - public class RedisReadJournal : - IReadJournal, - IPersistenceIdsQuery, - ICurrentPersistenceIdsQuery, - IEventsByPersistenceIdQuery, - ICurrentEventsByPersistenceIdQuery, - ICurrentEventsByTagQuery, - IEventsByTagQuery - { - private readonly ExtendedActorSystem _system; - private readonly Config _config; - - private ConnectionMultiplexer _redis; - private int _database; - - /// - /// The default identifier for to be used with . - /// - public static string Identifier = "akka.persistence.query.journal.redis"; - - public RedisReadJournal(ExtendedActorSystem system, Config config) - { - _system = system; - _config = config; - var address = system.Settings.Config.GetString("akka.persistence.journal.redis.configuration-string"); - - _database = system.Settings.Config.GetInt("akka.persistence.journal.redis.database"); - _redis = ConnectionMultiplexer.Connect(address); - } - - /// - /// Returns the live stream of persisted identifiers. Identifiers may appear several times in the stream. - /// - public Source PersistenceIds() => - Source.FromGraph(new PersistenceIdsSource(_redis, _database, _system)); - - /// - /// Returns the stream of current persisted identifiers. This stream is not live, once the identifiers were all returned, it is closed. - /// - public Source CurrentPersistenceIds() => - Source.FromGraph(new CurrentPersistenceIdsSource(_redis, _database, _system)); - - /// - /// Returns the live stream of events for the given . - /// Events are ordered by . - /// When the has been delivered, the stream is closed. - /// - public Source EventsByPersistenceId(string persistenceId, long fromSequenceNr = 0L, - long toSequenceNr = long.MaxValue) => - Source.FromGraph(new EventsByPersistenceIdSource(_redis, _database, _config, persistenceId, fromSequenceNr, - toSequenceNr, _system, true)); - - /// - /// Returns the stream of current events for the given . - /// Events are ordered by . - /// When the has been delivered or no more elements are available at the current time, the stream is closed. - /// - public Source CurrentEventsByPersistenceId(string persistenceId, - long fromSequenceNr = 0L, long toSequenceNr = long.MaxValue) => - Source.FromGraph(new EventsByPersistenceIdSource(_redis, _database, _config, persistenceId, fromSequenceNr, - toSequenceNr, _system, false)); - - /// - /// Returns the live stream of events with a given tag. - /// The events are sorted in the order they occurred, you can rely on it. - /// - public Source CurrentEventsByTag(string tag, Offset offset) - { - offset = offset ?? new Sequence(0L); - switch (offset) - { - case Sequence seq: - return Source.FromGraph(new EventsByTagSource(_redis, _database, _config, tag, seq.Value, _system, false)); - case NoOffset _: - return CurrentEventsByTag(tag, new Sequence(0L)); - default: - throw new ArgumentException($"RedisReadJournal does not support {offset.GetType().Name} offsets"); - } - } - - /// - /// Returns the stream of current events with a given tag. - /// The events are sorted in the order they occurred, you can rely on it. - /// Once there are no more events in the store, the stream is closed, not waiting for new ones. - /// - public Source EventsByTag(string tag, Offset offset) - { - offset = offset ?? new Sequence(0L); - switch (offset) - { - case Sequence seq: - return Source.FromGraph(new EventsByTagSource(_redis, _database, _config, tag, seq.Value, _system, true)); - case NoOffset _: - return EventsByTag(tag, new Sequence(0L)); - default: - throw new ArgumentException($"RedisReadJournal does not support {offset.GetType().Name} offsets"); - } - } - } -} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis/Query/RedisReadJournalProvider.cs b/src/Akka.Persistence.Redis/Query/RedisReadJournalProvider.cs deleted file mode 100644 index 27cad97..0000000 --- a/src/Akka.Persistence.Redis/Query/RedisReadJournalProvider.cs +++ /dev/null @@ -1,29 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using Akka.Actor; -using Akka.Configuration; -using Akka.Persistence.Query; - -namespace Akka.Persistence.Redis.Query -{ - public class RedisReadJournalProvider : IReadJournalProvider - { - private readonly ExtendedActorSystem _system; - private readonly Config _config; - - public RedisReadJournalProvider(ExtendedActorSystem system, Config config) - { - _system = system; - _config = config; - } - - public IReadJournal GetReadJournal() - { - return new RedisReadJournal(_system, _config); - } - } -} diff --git a/src/Akka.Persistence.Redis/Query/Stages/CurrentPersistenceIdsSource.cs b/src/Akka.Persistence.Redis/Query/Stages/CurrentPersistenceIdsSource.cs deleted file mode 100644 index 1013a10..0000000 --- a/src/Akka.Persistence.Redis/Query/Stages/CurrentPersistenceIdsSource.cs +++ /dev/null @@ -1,107 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using System; -using Akka.Streams.Stage; -using System.Collections.Generic; -using Akka.Actor; -using Akka.Persistence.Redis.Journal; -using Akka.Streams; -using Akka.Util.Internal; -using StackExchange.Redis; - -namespace Akka.Persistence.Redis.Query.Stages -{ - internal class CurrentPersistenceIdsSource : GraphStage> - { - private readonly ConnectionMultiplexer _redis; - private readonly int _database; - private readonly ExtendedActorSystem _system; - - public CurrentPersistenceIdsSource(ConnectionMultiplexer redis, int database, ExtendedActorSystem system) - { - _redis = redis; - _database = database; - _system = system; - } - - public Outlet Outlet { get; } = new Outlet(nameof(CurrentPersistenceIdsSource)); - - public override SourceShape Shape => new SourceShape(Outlet); - - protected override GraphStageLogic CreateLogic(Attributes inheritedAttributes) - { - return new CurrentPersistenceIdsLogic(_redis.GetDatabase(_database), _system, Outlet, Shape); - } - - private sealed class CurrentPersistenceIdsLogic : GraphStageLogic - { - private bool _start = true; - private long _index = 0L; - private readonly Queue _buffer = new Queue(); - private readonly Outlet _outlet; - private readonly JournalHelper _journalHelper; - - public CurrentPersistenceIdsLogic(IDatabase redisDatabase, ExtendedActorSystem system, Outlet outlet, Shape shape) : base(shape) - { - _outlet = outlet; - _journalHelper = new JournalHelper(system, system.Settings.Config.GetString("akka.persistence.journal.redis.key-prefix")); - - SetHandler(outlet, onPull: () => - { - if (_buffer.Count == 0 && (_start || _index > 0)) - { - var callback = GetAsyncCallback>(data => - { - // save the index for further initialization if needed - _index = data.AsInstanceOf().Cursor; - - // it is not the start anymore - _start = false; - - // enqueue received data - try - { - foreach (var item in data) - { - _buffer.Enqueue(item); - } - } - catch (Exception e) - { - Log.Error(e, "Error while querying persistence identifiers"); - FailStage(e); - } - - // deliver element - Deliver(); - }); - - callback(redisDatabase.SetScan(_journalHelper.GetIdentifiersKey(), cursor: _index)); - } - else - { - Deliver(); - } - }); - } - - private void Deliver() - { - if (_buffer.Count > 0) - { - var elem = _buffer.Dequeue(); - Push(_outlet, elem); - } - else - { - // we're done here, goodbye - CompleteStage(); - } - } - } - } -} diff --git a/src/Akka.Persistence.Redis/Query/Stages/EventsByPersistenceIdSource.cs b/src/Akka.Persistence.Redis/Query/Stages/EventsByPersistenceIdSource.cs deleted file mode 100644 index 6251dcc..0000000 --- a/src/Akka.Persistence.Redis/Query/Stages/EventsByPersistenceIdSource.cs +++ /dev/null @@ -1,359 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using Akka.Persistence.Query; -using StackExchange.Redis; -using System; -using Akka.Streams; -using Akka.Streams.Stage; -using Akka.Actor; -using Akka.Configuration; -using System.Collections.Generic; -using System.Linq; -using Akka.Pattern; - -namespace Akka.Persistence.Redis.Query.Stages -{ - internal class EventsByPersistenceIdSource : GraphStage> - { - private readonly ConnectionMultiplexer _redis; - private readonly int _database; - private readonly Config _config; - private readonly string _persistenceId; - private readonly long _fromSequenceNr; - private readonly long _toSequenceNr; - private readonly ActorSystem _system; - private readonly bool _live; - - public EventsByPersistenceIdSource(ConnectionMultiplexer redis, int database, Config config, string persistenceId, long fromSequenceNr, long toSequenceNr, ActorSystem system, bool live) - { - _redis = redis; - _database = database; - _config = config; - _persistenceId = persistenceId; - _fromSequenceNr = fromSequenceNr; - _toSequenceNr = toSequenceNr; - _system = system; - _live = live; - - Outlet = live - ? new Outlet("EventsByPersistenceIdSource") - : new Outlet("CurrentEventsByPersistenceIdSource"); - - Shape = new SourceShape(Outlet); - } - - internal Outlet Outlet { get; } - - public override SourceShape Shape { get; } - - protected override GraphStageLogic CreateLogic(Attributes inheritedAttributes) - { - return new EventsByPersistenceIdLogic(_redis, _database, _config, _system, _persistenceId, _fromSequenceNr, _toSequenceNr, _live, Outlet, Shape); - } - - private enum State - { - Idle = 0, - Querying = 1, - NotifiedWhenQuerying = 2, - WaitingForNotification = 3, - Initializing = 4, - QueryWhenInitializing = 5 - } - - private class EventsByPersistenceIdLogic : GraphStageLogic - { - private State _state = State.Idle; - private readonly Queue _buffer = new Queue(); - private ISubscriber _subscription; - private readonly int _max; - private readonly JournalHelper _journalHelper; - private long _currentSequenceNr; - private Action> _callback; - - private readonly Outlet _outlet; - private readonly ConnectionMultiplexer _redis; - private readonly int _database; - private readonly ActorSystem _system; - private readonly string _persistenceId; - private long _toSequenceNr; - private readonly bool _live; - - public EventsByPersistenceIdLogic( - ConnectionMultiplexer redis, - int database, - Config config, - ActorSystem system, - string persistenceId, - long fromSequenceNr, - long toSequenceNr, - bool live, - Outlet outlet, Shape shape) : base(shape) - { - _outlet = outlet; - _redis = redis; - _database = database; - _system = system; - _persistenceId = persistenceId; - _toSequenceNr = toSequenceNr; - _live = live; - - _max = config.GetInt("max-buffer-size"); - _journalHelper = new JournalHelper(system, system.Settings.Config.GetString("akka.persistence.journal.redis.key-prefix")); - - _currentSequenceNr = fromSequenceNr; - SetHandler(outlet, onPull: () => - { - switch (_state) - { - case State.Initializing: - _state = State.QueryWhenInitializing; - break; - default: - Query(); - break; - } - }); - } - - public override void PreStart() - { - _callback = GetAsyncCallback>(events => - { - if (events.Count == 0) - { - if (_currentSequenceNr > _toSequenceNr) - { - // end has been reached - CompleteStage(); - } - else - { - switch (_state) - { - case State.NotifiedWhenQuerying: - // maybe we missed some new event when querying, retry - _state = State.Idle; - Query(); - break; - case State.Querying: - if (_live) - { - // nothing new, wait for notification - _state = State.WaitingForNotification; - } - else - { - // not a live stream, nothing else currently in the database, close the stream - CompleteStage(); - } - break; - default: - Log.Error($"Unexpected source state: {_state}"); - FailStage(new IllegalStateException($"Unexpected source state: {_state}")); - break; - } - } - } - else - { - var (evts, maxSequenceNr) = events.Aggregate((new List(), _currentSequenceNr), (tuple, pr) => - { - if (!pr.IsDeleted && - pr.SequenceNr >= _currentSequenceNr && - pr.SequenceNr <= _toSequenceNr) - { - tuple.Item1.Add(new EventEnvelope(new Sequence(pr.SequenceNr), pr.PersistenceId, pr.SequenceNr, pr.Payload)); - tuple.Item2 = pr.SequenceNr + 1; - } - else - { - tuple.Item2 = pr.SequenceNr + 1; - } - - return tuple; - }); - - _currentSequenceNr = maxSequenceNr; - Log.Debug($"Max sequence number is now {maxSequenceNr}"); - if (evts.Count > 0) - { - evts.ForEach(_buffer.Enqueue); - Deliver(); - } - else - { - // requery immediately - _state = State.Idle; - Query(); - } - } - }); - - if (_live) - { - // subscribe to notification stream only if live stream was required - var messageCallback = GetAsyncCallback<(RedisChannel channel, string bs)>(data => - { - if (data.channel.Equals(_journalHelper.GetJournalChannel(_persistenceId))) - { - Log.Debug("Message received"); - - switch (_state) - { - case State.Idle: - // do nothing, no query is running and no client request was performed - break; - case State.Querying: - _state = State.NotifiedWhenQuerying; - break; - case State.NotifiedWhenQuerying: - // do nothing we already know that some new events may exist - break; - case State.WaitingForNotification: - _state = State.Idle; - Query(); - break; - } - } - else - { - Log.Debug($"Message from unexpected channel: {data.channel}"); - } - }); - - _subscription = _redis.GetSubscriber(); - _subscription.Subscribe(_journalHelper.GetJournalChannel(_persistenceId), (channel, value) => - { - messageCallback.Invoke((channel, value)); - }); - } - else - { - // start by first querying the current highest sequenceNr - // for the given persistent id - // stream will stop once this has been delivered - _state = State.Initializing; - - var initCallback = GetAsyncCallback(sn => - { - if (_toSequenceNr > sn) - { - // the initially requested max sequence number is higher than the current - // one, restrict it to the current one - _toSequenceNr = sn; - } - - switch (_state) - { - case State.QueryWhenInitializing: - // during initialization, downstream asked for an element, - // let’s query elements - _state = State.Idle; - Query(); - break; - case State.Initializing: - // no request from downstream, just go idle - _state = State.Idle; - break; - default: - Log.Error($"Unexpected source state when initializing: {_state}"); - FailStage(new IllegalStateException($"Unexpected source state when initializing: {_state}")); - break; - } - }); - - _redis.GetDatabase(_database).StringGetAsync(_journalHelper.GetHighestSequenceNrKey(_persistenceId)).ContinueWith(task => - { - if (!task.IsCanceled || task.IsFaulted) - { - if (task.Result.IsNull == true) - { - // not found, close - CompleteStage(); - } - else - { - initCallback(long.Parse(task.Result)); - } - } - else - { - Log.Error(task.Exception, "Error while initializing current events by persistent id"); - FailStage(task.Exception); - } - }); - } - } - - public override void PostStop() - { - _subscription?.UnsubscribeAll(); - } - - private void Query() - { - switch (_state) - { - case State.Idle: - if (_buffer.Count == 0) - { - // so, we need to fill this buffer - _state = State.Querying; - - // Complete stage if fromSequenceNr is higher than toSequenceNr - if (_toSequenceNr < _currentSequenceNr) - { - CompleteStage(); - } - - _redis.GetDatabase(_database).SortedSetRangeByScoreAsync( - key: _journalHelper.GetJournalKey(_persistenceId), - start: _currentSequenceNr, - stop: Math.Min(_currentSequenceNr + _max - 1, _toSequenceNr), - order: Order.Ascending).ContinueWith(task => - { - if (!task.IsCanceled || task.IsFaulted) - { - var deserializedEvents = task.Result.Select(e => _journalHelper.PersistentFromBytes(e)).ToList(); - _callback(deserializedEvents); - } - else - { - Log.Error(task.Exception, "Error while querying events by persistence identifier"); - FailStage(task.Exception); - } - }); - } - else - { - // buffer is non empty, let’s deliver buffered data - Deliver(); - } - break; - default: - Log.Error($"Unexpected source state when querying: {_state}"); - FailStage(new IllegalStateException($"Unexpected source state when querying: {_state}")); - break; - } - } - - private void Deliver() - { - // go back to idle state, waiting for more client request - _state = State.Idle; - var elem = _buffer.Dequeue(); - Push(_outlet, elem); - if (_buffer.Count == 0 && _currentSequenceNr > _toSequenceNr) - { - // we delivered last buffered event and the upper bound was reached, complete - CompleteStage(); - } - } - } - } -} diff --git a/src/Akka.Persistence.Redis/Query/Stages/EventsByTagSource.cs b/src/Akka.Persistence.Redis/Query/Stages/EventsByTagSource.cs deleted file mode 100644 index 367b2a3..0000000 --- a/src/Akka.Persistence.Redis/Query/Stages/EventsByTagSource.cs +++ /dev/null @@ -1,386 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Akka.Actor; -using Akka.Configuration; -using Akka.Pattern; -using Akka.Persistence.Query; -using Akka.Persistence.Redis.Journal; -using Akka.Streams; -using Akka.Streams.Stage; -using Akka.Util.Internal; -using StackExchange.Redis; - -namespace Akka.Persistence.Redis.Query.Stages -{ - internal class EventsByTagSource : GraphStage> - { - private readonly ConnectionMultiplexer _redis; - private readonly int _database; - private readonly Config _config; - private readonly string _tag; - private readonly long _offset; - private readonly ExtendedActorSystem _system; - private readonly bool _live; - - public EventsByTagSource(ConnectionMultiplexer redis, int database, Config config, string tag, long offset, ExtendedActorSystem system, bool live) - { - _redis = redis; - _database = database; - _config = config; - _tag = tag; - _offset = offset; - _system = system; - _live = live; - - Outlet = live - ? new Outlet("EventsByTagSource") - : new Outlet("CurrentEventsByTagSource"); - - Shape = new SourceShape(Outlet); - } - - internal Outlet Outlet { get; } - - public override SourceShape Shape { get; } - - protected override GraphStageLogic CreateLogic(Attributes inheritedAttributes) - { - return new EventsByTagLogic(_redis, _database, _config, _system, _tag, _offset, _live, Outlet, Shape); - } - - private enum State - { - Idle = 0, - Querying = 1, - NotifiedWhenQuerying = 2, - WaitingForNotification = 3, - Initializing = 4, - QueryWhenInitializing = 5 - } - - private class EventsByTagLogic : GraphStageLogic - { - private State _state = State.Idle; - private readonly Queue _buffer = new Queue(); - private ISubscriber _subscription; - private readonly int _max; - private readonly JournalHelper _journalHelper; - private Action<(int, IReadOnlyList<(string, IPersistentRepresentation)>)> _callback; - - private readonly Outlet _outlet; - private readonly ConnectionMultiplexer _redis; - private readonly int _database; - private readonly ActorSystem _system; - private readonly long _offset; - private readonly string _tag; - private readonly bool _live; - - private long _currentOffset; - private long _maxOffset = long.MaxValue; - - public EventsByTagLogic( - ConnectionMultiplexer redis, - int database, - Config config, - ActorSystem system, - string tag, - long offset, - bool live, - Outlet outlet, Shape shape) : base(shape) - { - _outlet = outlet; - _redis = redis; - _database = database; - _system = system; - _offset = offset; - _tag = tag; - _live = live; - - _max = config.GetInt("max-buffer-size"); - _journalHelper = new JournalHelper(system, system.Settings.Config.GetString("akka.persistence.journal.redis.key-prefix")); - - _currentOffset = offset > 0 ? offset + 1 : 0; - - SetHandler(outlet, onPull: () => - { - switch (_state) - { - case State.Initializing: - _state = State.QueryWhenInitializing; - break; - default: - Query(); - break; - } - }); - } - - public override void PreStart() - { - _callback = GetAsyncCallback<(int, IReadOnlyList<(string, IPersistentRepresentation)>)>(raw => - { - var nb = raw.Item1; - var events = raw.Item2; - - if (events.Count == 0) - { - if (_currentOffset >= _maxOffset) - { - // end has been reached - CompleteStage(); - } - else - { - switch (_state) - { - case State.NotifiedWhenQuerying: - // maybe we missed some new event when querying, retry - _state = State.Idle; - Query(); - break; - case State.Querying: - if (_live) - { - // nothing new, wait for notification - _state = State.WaitingForNotification; - } - else - { - // not a live stream, nothing else currently in the database, close the stream - CompleteStage(); - } - break; - default: - Log.Error($"Unexpected source state: {_state}"); - FailStage(new IllegalStateException($"Unexpected source state: {_state}")); - break; - } - } - } - else - { - var evts = events.ZipWithIndex().Select(c => - { - var repr = c.Key.Item2; - if (repr != null && !repr.IsDeleted) - { - return new EventEnvelope(new Sequence(_currentOffset + c.Value), repr.PersistenceId, repr.SequenceNr, repr.Payload); - } - - return null; - }).ToList(); - - _currentOffset += nb; - if (evts.Count > 0) - { - evts.ForEach(_buffer.Enqueue); - Deliver(); - } - else - { - // requery immediately - _state = State.Idle; - Query(); - } - } - }); - - if (_live) - { - // subscribe to notification stream only if live stream was required - var messageCallback = GetAsyncCallback<(RedisChannel channel, string bs)>(data => - { - if (data.channel.Equals(_journalHelper.GetTagsChannel()) && data.bs == _tag) - { - Log.Debug("Message received"); - - switch (_state) - { - case State.Idle: - // do nothing, no query is running and no client request was performed - break; - case State.Querying: - _state = State.NotifiedWhenQuerying; - break; - case State.NotifiedWhenQuerying: - // do nothing we already know that some new events may exist - break; - case State.WaitingForNotification: - _state = State.Idle; - Query(); - break; - } - } - else if (data.channel.Equals(_journalHelper.GetTagsChannel())) - { - // ignore other tags - } - else - { - Log.Debug($"Message from unexpected channel: {data.channel}"); - } - }); - - _subscription = _redis.GetSubscriber(); - _subscription.Subscribe(_journalHelper.GetTagsChannel(), (channel, value) => - { - messageCallback.Invoke((channel, value)); - }); - } - else - { - // start by first querying the current length of tag events - // for the given tag - // stream will stop once this has been delivered - _state = State.Initializing; - - var initCallback = GetAsyncCallback(len => - { - _maxOffset = len; - switch (_state) - { - case State.QueryWhenInitializing: - // during initialization, downstream asked for an element, - // let’s query elements - _state = State.Idle; - Query(); - break; - case State.Initializing: - // no request from downstream, just go idle - _state = State.Idle; - break; - default: - Log.Error($"Unexpected source state when initializing: {_state}"); - FailStage(new IllegalStateException($"Unexpected source state when initializing: {_state}")); - break; - } - }); - - _redis.GetDatabase(_database).ListLengthAsync(_journalHelper.GetTagKey(_tag)).ContinueWith(task => - { - if (!task.IsCanceled || task.IsFaulted) - { - initCallback(task.Result - 1); - } - else - { - Log.Error(task.Exception, "Error while initializing current events by tag"); - FailStage(task.Exception); - } - }); - } - } - - public override void PostStop() - { - _subscription?.UnsubscribeAll(); - } - - private void Query() - { - switch (_state) - { - case State.Idle: - if (_buffer.Count == 0) - { - // so, we need to fill this buffer - _state = State.Querying; - - // request next batch of events for this tag (potentially limiting to the max offset in the case of non live stream) - var refs = _redis.GetDatabase(_database).ListRange( - _journalHelper.GetTagKey(_tag), - _currentOffset, - Math.Min(_maxOffset, _currentOffset + _max - 1)); - - var trans = _redis.GetDatabase(_database).CreateTransaction(); - - var events = refs.Select(bytes => - { - var (sequenceNr, persistenceId) = bytes.Deserialize(); - return trans.SortedSetRangeByScoreAsync(_journalHelper.GetJournalKey(persistenceId), sequenceNr, sequenceNr); - }).ToList(); - - trans.ExecuteAsync().ContinueWith(task => - { - if (!task.IsCanceled || task.IsFaulted) - { - var callbackEvents = events.Select(bytes => - { - var result = _journalHelper.PersistentFromBytes(bytes.Result.FirstOrDefault()); - return (result.PersistenceId, result); - }).ToList(); - _callback((refs.Length, callbackEvents)); - } - else - { - Log.Error(task.Exception, "Error while querying events by persistence identifier"); - FailStage(task.Exception); - } - }); - } - else - { - // buffer is non empty, let’s deliver buffered data - Deliver(); - } - break; - default: - Log.Error($"Unexpected source state when querying: {_state}"); - FailStage(new IllegalStateException($"Unexpected source state when querying: {_state}")); - break; - } - } - - private void Deliver() - { - // go back to idle state, waiting for more client request - _state = State.Idle; - var elem = _buffer.Dequeue(); - Push(_outlet, elem); - if (_buffer.Count == 0 && _currentOffset >= _maxOffset) - { - // max offset has been reached and delivered, complete - CompleteStage(); - } - } - } - } - - internal static class EventRefDeserializer - { - private static readonly Regex EventRef = new Regex("(\\d+):(.*)"); - - public static (long, string) Deserialize(this RedisValue value) - { - var match = EventRef.Match(value); - - if (match.Success) - return (long.Parse(match.Groups[1].Value), match.Groups[2].Value); - else - throw new SerializationException($"Unable to deserializer {value}"); - } - - public static Dictionary ZipWithIndex(this IEnumerable collection) - { - var i = 0; - var dict = new Dictionary(); - foreach (var item in collection) - { - dict.Add(item, i); - i++; - } - return dict; - } - } -} diff --git a/src/Akka.Persistence.Redis/Query/Stages/PersistenceIdsSource.cs b/src/Akka.Persistence.Redis/Query/Stages/PersistenceIdsSource.cs deleted file mode 100644 index 2e171fb..0000000 --- a/src/Akka.Persistence.Redis/Query/Stages/PersistenceIdsSource.cs +++ /dev/null @@ -1,148 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib -// -//----------------------------------------------------------------------- - -using System; -using Akka.Streams.Stage; -using System.Collections.Generic; -using Akka.Actor; -using Akka.Persistence.Redis.Journal; -using Akka.Streams; -using Akka.Util.Internal; -using StackExchange.Redis; - -namespace Akka.Persistence.Redis.Query.Stages -{ - internal class PersistenceIdsSource : GraphStage> - { - private readonly ConnectionMultiplexer _redis; - private readonly int _database; - private readonly ExtendedActorSystem _system; - - public PersistenceIdsSource(ConnectionMultiplexer redis, int database, ExtendedActorSystem system) - { - _redis = redis; - _database = database; - _system = system; - } - - public Outlet Outlet { get; } = new Outlet(nameof(PersistenceIdsSource)); - - public override SourceShape Shape => new SourceShape(Outlet); - - protected override GraphStageLogic CreateLogic(Attributes inheritedAttributes) - { - return new PersistenceIdsLogic(_redis, _database, _system, Outlet, Shape); - } - - private class PersistenceIdsLogic : GraphStageLogic - { - private bool _start = true; - private long _index = 0; - private readonly Queue _buffer = new Queue(); - private bool _downstreamWaiting = false; - private ISubscriber _subscription; - - private readonly Outlet _outlet; - private readonly ConnectionMultiplexer _redis; - private readonly int _database; - private readonly JournalHelper _journalHelper; - - public PersistenceIdsLogic(ConnectionMultiplexer redis, int database, ExtendedActorSystem system, Outlet outlet, Shape shape) : base(shape) - { - _redis = redis; - _database = database; - _journalHelper = new JournalHelper(system, system.Settings.Config.GetString("akka.persistence.journal.redis.key-prefix")); - _outlet = outlet; - - SetHandler(outlet, onPull: () => - { - _downstreamWaiting = true; - if (_buffer.Count == 0 && (_start || _index > 0)) - { - var callback = GetAsyncCallback>(data => - { - // save the index for further initialization if needed - _index = data.AsInstanceOf().Cursor; - - // it is not the start anymore - _start = false; - - // enqueue received data - try - { - foreach (var item in data) - { - _buffer.Enqueue(item); - } - } - catch (Exception e) - { - Log.Error(e, "Error while querying persistence identifiers"); - FailStage(e); - } - - // deliver element - Deliver(); - }); - - callback(_redis.GetDatabase(_database).SetScan(_journalHelper.GetIdentifiersKey(), cursor: _index)); - } - else if (_buffer.Count == 0) - { - // wait for asynchornous notification and mark dowstream - // as waiting for data - } - else - { - Deliver(); - } - }); - } - - public override void PreStart() - { - var callback = GetAsyncCallback<(RedisChannel channel, string bs)>(data => - { - if (data.channel.Equals(_journalHelper.GetIdentifiersChannel())) - { - Log.Debug("Message received"); - - // enqueue the element - _buffer.Enqueue(data.bs); - - // deliver element - Deliver(); - } - else - { - Log.Debug($"Message from unexpected channel: {data.channel}"); - } - }); - - _subscription = _redis.GetSubscriber(); - _subscription.Subscribe(_journalHelper.GetIdentifiersChannel(), (channel, value) => - { - callback.Invoke((channel, value)); - }); - } - - public override void PostStop() - { - _subscription?.UnsubscribeAll(); - } - - private void Deliver() - { - if (_downstreamWaiting) - { - _downstreamWaiting = false; - var elem = _buffer.Dequeue(); - Push(_outlet, elem); - } - } - } - } -} diff --git a/src/Akka.Persistence.Redis/RedisExtensions.cs b/src/Akka.Persistence.Redis/RedisExtensions.cs new file mode 100644 index 0000000..a3c5ed0 --- /dev/null +++ b/src/Akka.Persistence.Redis/RedisExtensions.cs @@ -0,0 +1,24 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using StackExchange.Redis; + +namespace Akka.Persistence.Redis +{ + public static class RedisExtensions + { + public static bool IsClustered(this IConnectionMultiplexer connection) + { + return connection.GetEndPoints() + .Select(endPoint => connection.GetServer(endPoint)) + .Any(server => server.ServerType == ServerType.Cluster); + } + } +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis/RedisPersistence.cs b/src/Akka.Persistence.Redis/RedisPersistence.cs index f1ae6c2..7538926 100644 --- a/src/Akka.Persistence.Redis/RedisPersistence.cs +++ b/src/Akka.Persistence.Redis/RedisPersistence.cs @@ -1,8 +1,8 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2017 Akka.NET Contrib +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using System; using Akka.Actor; @@ -31,16 +31,23 @@ public static RedisSettings Create(Config config) throw new ArgumentNullException(nameof(config)); return new RedisSettings( - configurationString: config.GetString("configuration-string", string.Empty), - keyPrefix: config.GetString("key-prefix", string.Empty), - database: config.GetInt("database", 0)); + config.GetString("configuration-string", string.Empty), + config.GetString("key-prefix", string.Empty), + config.GetInt("database", 0)); } } public class RedisPersistence : IExtension { - public static RedisPersistence Get(ActorSystem system) => system.WithExtension(); - public static Config DefaultConfig() => ConfigurationFactory.FromResource("Akka.Persistence.Redis.reference.conf"); + public static RedisPersistence Get(ActorSystem system) + { + return system.WithExtension(); + } + + public static Config DefaultConfig() + { + return ConfigurationFactory.FromResource("Akka.Persistence.Redis.reference.conf"); + } public RedisSettings JournalSettings { get; } public RedisSettings SnapshotStoreSettings { get; } @@ -50,7 +57,8 @@ public RedisPersistence(ExtendedActorSystem system) system.Settings.InjectTopLevelFallback(DefaultConfig()); JournalSettings = RedisSettings.Create(system.Settings.Config.GetConfig("akka.persistence.journal.redis")); - SnapshotStoreSettings = RedisSettings.Create(system.Settings.Config.GetConfig("akka.persistence.snapshot-store.redis")); + SnapshotStoreSettings = + RedisSettings.Create(system.Settings.Config.GetConfig("akka.persistence.snapshot-store.redis")); } } @@ -61,4 +69,4 @@ public override RedisPersistence CreateExtension(ExtendedActorSystem system) return new RedisPersistence(system); } } -} +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis/Serialization/PersistentSnapshotSerializer.cs b/src/Akka.Persistence.Redis/Serialization/PersistentSnapshotSerializer.cs index 243eb67..589a2de 100644 --- a/src/Akka.Persistence.Redis/Serialization/PersistentSnapshotSerializer.cs +++ b/src/Akka.Persistence.Redis/Serialization/PersistentSnapshotSerializer.cs @@ -1,4 +1,10 @@ -using System; +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System; using System.Runtime.Serialization; using Akka.Actor; using Akka.Persistence.Serialization.Proto.Msg; @@ -20,10 +26,11 @@ public override byte[] ToBinary(object obj) { if (obj is SelectedSnapshot snapshot) { - SnapshotMessage snapshotMessage = new SnapshotMessage(); + var snapshotMessage = new SnapshotMessage(); snapshotMessage.PersistenceId = snapshot.Metadata.PersistenceId; snapshotMessage.SequenceNr = snapshot.Metadata.SequenceNr; - snapshotMessage.TimeStamp = Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(snapshot.Metadata.Timestamp); + snapshotMessage.TimeStamp = + Google.Protobuf.WellKnownTypes.Timestamp.FromDateTime(snapshot.Metadata.Timestamp); snapshotMessage.Payload = GetPersistentPayload(snapshot.Snapshot); return snapshotMessage.ToByteArray(); } @@ -45,25 +52,24 @@ public override object FromBinary(byte[] bytes, Type type) GetPayload(snapshotMessage.Payload)); } - throw new SerializationException($"Unimplemented deserialization of message with type [{type}] in [{GetType()}]"); + throw new SerializationException( + $"Unimplemented deserialization of message with type [{type}] in [{GetType()}]"); } private PersistentPayload GetPersistentPayload(object obj) { - Serializer serializer = system.Serialization.FindSerializerFor(obj); + var serializer = system.Serialization.FindSerializerFor(obj); var payload = new PersistentPayload(); if (serializer is SerializerWithStringManifest serializer2) { - string manifest = serializer2.Manifest(obj); + var manifest = serializer2.Manifest(obj); payload.PayloadManifest = ByteString.CopyFromUtf8(manifest); } else { if (serializer.IncludeManifest) - { payload.PayloadManifest = ByteString.CopyFromUtf8(obj.GetType().TypeQualifiedName()); - } } payload.Payload = ByteString.CopyFrom(serializer.ToBinary(obj)); @@ -74,10 +80,10 @@ private PersistentPayload GetPersistentPayload(object obj) private object GetPayload(PersistentPayload payload) { - string manifest = ""; + var manifest = ""; if (payload.PayloadManifest != null) manifest = payload.PayloadManifest.ToStringUtf8(); return system.Serialization.Deserialize(payload.Payload.ToByteArray(), payload.SerializerId, manifest); } } -} +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis/Snapshot/RedisSnapshotStore.cs b/src/Akka.Persistence.Redis/Snapshot/RedisSnapshotStore.cs index 2c43c09..ed3ae0a 100644 --- a/src/Akka.Persistence.Redis/Snapshot/RedisSnapshotStore.cs +++ b/src/Akka.Persistence.Redis/Snapshot/RedisSnapshotStore.cs @@ -1,8 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2017 Akka.NET Contrib +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using System; using System.Linq; @@ -20,6 +20,8 @@ public class RedisSnapshotStore : SnapshotStore private ActorSystem _system; public IDatabase Database => _database.Value; + public bool IsClustered { get; private set; } + public RedisSnapshotStore() { _settings = RedisPersistence.Get(Context.System).SnapshotStoreSettings; @@ -33,18 +35,20 @@ protected override void PreStart() _database = new Lazy(() => { var redisConnection = ConnectionMultiplexer.Connect(_settings.ConfigurationString); + IsClustered = redisConnection.IsClustered(); return redisConnection.GetDatabase(_settings.Database); }); } - protected override async Task LoadAsync(string persistenceId, SnapshotSelectionCriteria criteria) + protected override async Task LoadAsync(string persistenceId, + SnapshotSelectionCriteria criteria) { var snapshots = await Database.SortedSetRangeByScoreAsync( - GetSnapshotKey(persistenceId), - criteria.MaxSequenceNr, - -1, - Exclude.None, - Order.Descending); + GetSnapshotKey(persistenceId, IsClustered), + criteria.MaxSequenceNr, + -1, + Exclude.None, + Order.Descending); var found = snapshots .Select(c => PersistentFromBytes(c)) @@ -59,30 +63,33 @@ protected override async Task LoadAsync(string persistenceId, protected override Task SaveAsync(SnapshotMetadata metadata, object snapshot) { return Database.SortedSetAddAsync( - GetSnapshotKey(metadata.PersistenceId), - PersistentToBytes(metadata, snapshot), - metadata.SequenceNr); + GetSnapshotKey(metadata.PersistenceId, IsClustered), + PersistentToBytes(metadata, snapshot), + metadata.SequenceNr); } protected override async Task DeleteAsync(SnapshotMetadata metadata) { - await Database.SortedSetRemoveRangeByScoreAsync(GetSnapshotKey(metadata.PersistenceId), metadata.SequenceNr, metadata.SequenceNr); + await Database.SortedSetRemoveRangeByScoreAsync(GetSnapshotKey(metadata.PersistenceId, IsClustered), metadata.SequenceNr, + metadata.SequenceNr); } protected override async Task DeleteAsync(string persistenceId, SnapshotSelectionCriteria criteria) { var snapshots = await Database.SortedSetRangeByScoreAsync( - GetSnapshotKey(persistenceId), - criteria.MaxSequenceNr, - 0L, - Exclude.None, - Order.Descending); + GetSnapshotKey(persistenceId, IsClustered), + criteria.MaxSequenceNr, + 0L, + Exclude.None, + Order.Descending); var found = snapshots - .Select(c => PersistentFromBytes(c)) - .Where(snapshot => snapshot.Metadata.Timestamp <= criteria.MaxTimeStamp && snapshot.Metadata.SequenceNr <= criteria.MaxSequenceNr) - .Select(s => _database.Value.SortedSetRemoveRangeByScoreAsync(GetSnapshotKey(persistenceId), s.Metadata.SequenceNr, s.Metadata.SequenceNr)) - .ToArray(); + .Select(c => PersistentFromBytes(c)) + .Where(snapshot => snapshot.Metadata.Timestamp <= criteria.MaxTimeStamp && + snapshot.Metadata.SequenceNr <= criteria.MaxSequenceNr) + .Select(s => _database.Value.SortedSetRemoveRangeByScoreAsync(GetSnapshotKey(persistenceId, IsClustered), + s.Metadata.SequenceNr, s.Metadata.SequenceNr)) + .ToArray(); await Task.WhenAll(found); } @@ -91,7 +98,7 @@ private byte[] PersistentToBytes(SnapshotMetadata metadata, object snapshot) { var message = new SelectedSnapshot(metadata, snapshot); var serializer = _system.Serialization.FindSerializerForType(typeof(SelectedSnapshot)); - return Akka.Serialization.Serialization.WithTransport(_system as ExtendedActorSystem, + return Akka.Serialization.Serialization.WithTransport(_system as ExtendedActorSystem, () => serializer.ToBinary(message)); //return serializer.ToBinary(message); } @@ -102,15 +109,21 @@ private SelectedSnapshot PersistentFromBytes(byte[] bytes) return serializer.FromBinary(bytes); } - private string GetSnapshotKey(string persistenceId) => $"{_settings.KeyPrefix}snapshot:{persistenceId}"; + public string GetSnapshotKey(string persistenceId, bool withHashTag) + { + return withHashTag + ? $"{{__{persistenceId}}}.{_settings.KeyPrefix}snapshot:{persistenceId}" + : $"{_settings.KeyPrefix}snapshot:{persistenceId}"; + } } internal static class SnapshotMetadataExtensions { public static bool Matches(this SnapshotSelectionCriteria criteria, SnapshotMetadata metadata) { - return metadata.SequenceNr <= criteria.MaxSequenceNr && metadata.Timestamp <= criteria.MaxTimeStamp - && metadata.SequenceNr >= criteria.MinSequenceNr && metadata.Timestamp >= criteria.MinTimestamp; + return metadata.SequenceNr <= criteria.MaxSequenceNr && metadata.Timestamp <= criteria.MaxTimeStamp + && metadata.SequenceNr >= criteria.MinSequenceNr && + metadata.Timestamp >= criteria.MinTimestamp; } } -} +} \ No newline at end of file diff --git a/src/Akka.Persistence.Redis/reference.conf b/src/Akka.Persistence.Redis/reference.conf index ae60e02..86986f7 100644 --- a/src/Akka.Persistence.Redis/reference.conf +++ b/src/Akka.Persistence.Redis/reference.conf @@ -10,7 +10,8 @@ # dispatcher used to drive journal actor plugin-dispatcher = "akka.actor.default-dispatcher" - # Redis journals key prefixes. Leave it for default or change it to appropriate value. WARNING: don't change it on production instances. + # Redis journals key prefixes. Leave it for default or change it to appropriate value. + # WARNING: don't change it on production instances. key-prefix = "" } } diff --git a/src/benchmarks/Akka.Persistence.Redis.Benchmark.DockerTests/Akka.Persistence.Redis.Benchmark.DockerTests.csproj b/src/benchmarks/Akka.Persistence.Redis.Benchmark.DockerTests/Akka.Persistence.Redis.Benchmark.DockerTests.csproj new file mode 100644 index 0000000..92a6a51 --- /dev/null +++ b/src/benchmarks/Akka.Persistence.Redis.Benchmark.DockerTests/Akka.Persistence.Redis.Benchmark.DockerTests.csproj @@ -0,0 +1,36 @@ + + + + $(NetCoreTestVersion) + Akka.Persistence.Redis.BenchmarkTests.Docker + false + + + + false + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/src/benchmarks/Akka.Persistence.Redis.Benchmark.DockerTests/Docker/RedisJournalBenchmarks.cs b/src/benchmarks/Akka.Persistence.Redis.Benchmark.DockerTests/Docker/RedisJournalBenchmarks.cs new file mode 100644 index 0000000..f6dc978 --- /dev/null +++ b/src/benchmarks/Akka.Persistence.Redis.Benchmark.DockerTests/Docker/RedisJournalBenchmarks.cs @@ -0,0 +1,63 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System; +using Akka.Configuration; +using Akka.Persistence.Redis.BenchmarkTests.Docker; +using Akka.Persistence.Redis.Tests; +using Akka.Persistence.TestKit.Performance; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.Persistence.Redis.Benchmark.DockerTests +{ + public class TestConstants + { + public const int NumMessages = 1000; + public const int DockerNumMessages = 1000; + } + + [Collection("RedisBenchmark")] + public class RedisJournalPerfSpec : RedisJournalBenchmarkDefinitions, IClassFixture + { + public const int Database = 1; + + public static Config Config(RedisFixture fixture, int id) + { + DbUtils.Initialize(fixture); + + return ConfigurationFactory.ParseString($@" + akka.loglevel = INFO + akka.persistence.journal.plugin = ""akka.persistence.journal.redis"" + akka.persistence.journal.redis {{ + class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" + plugin-dispatcher = ""akka.actor.default-dispatcher"" + configuration-string = ""{fixture.ConnectionString}"" + database = {id} + }} + akka.test.single-expect-default = 3s") + .WithFallback(RedisPersistence.DefaultConfig()) + .WithFallback(Persistence.DefaultConfig()); + } + + public RedisJournalPerfSpec(ITestOutputHelper output, RedisFixture fixture) + : base(Config(fixture, Database), nameof(RedisJournalPerfSpec), output, 40, TestConstants.DockerNumMessages) + { + } + + [Fact] + public void PersistenceActor_Must_measure_PersistGroup1000() + { + RunGroupBenchmark(1000, 10); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + DbUtils.Clean(Database); + } + } +} \ No newline at end of file diff --git a/src/benchmarks/Akka.Persistence.Redis.Benchmark.DockerTests/RedisJournalBenchmarkDefinitions.cs b/src/benchmarks/Akka.Persistence.Redis.Benchmark.DockerTests/RedisJournalBenchmarkDefinitions.cs new file mode 100644 index 0000000..91380a7 --- /dev/null +++ b/src/benchmarks/Akka.Persistence.Redis.Benchmark.DockerTests/RedisJournalBenchmarkDefinitions.cs @@ -0,0 +1,587 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Akka.Actor; +using Akka.Configuration; +using Akka.Routing; +using Akka.TestKit; +using Akka.Util; +using Akka.Util.Internal; +using JetBrains.dotMemoryUnit; +using JetBrains.dotMemoryUnit.Kernel; +using Xunit; +using Xunit.Abstractions; + +namespace Akka.Persistence.Redis.BenchmarkTests.Docker +{ + public abstract class RedisJournalBenchmarkDefinitions : Akka.TestKit.Xunit2.TestKit + { + private TestProbe testProbe; + + // Number of messages sent to the PersistentActor under test for each test iteration + private int EventsCount; + + // Number of measurement iterations each test will be run. + private const int MeasurementIterations = 10; + + private IReadOnlyList Commands => Enumerable.Range(1, EventsCount).ToList(); + + private TimeSpan ExpectDuration; + + protected RedisJournalBenchmarkDefinitions(Config config, string actorSystem, ITestOutputHelper output, + int timeoutDurationSeconds = 30, int eventsCount = 10000) + : base(config ?? Config.Empty, actorSystem, output) + { + ThreadPool.SetMinThreads(12, 12); + EventsCount = eventsCount; + ExpectDuration = TimeSpan.FromSeconds(timeoutDurationSeconds); + testProbe = CreateTestProbe(); + } + + internal IActorRef BenchActor(string pid, int replyAfter) + { + return Sys.ActorOf(Props.Create(() => new BenchActor(pid, testProbe, EventsCount))); + ; + } + + internal (IActorRef aut, TestProbe probe) BenchActorNewProbe(string pid, int replyAfter) + { + var tp = CreateTestProbe(); + return (Sys.ActorOf(Props.Create(() => new BenchActor(pid, tp, EventsCount))), tp); + } + + internal (IActorRef aut, TestProbe probe) BenchActorNewProbeGroup(string pid, int numActors, int numMsgs) + { + var tp = CreateTestProbe(); + return ( + Sys.ActorOf(Props + .Create(() => + new BenchActor(pid, tp, numMsgs, false)) + .WithRouter(new RoundRobinPool(numActors))), tp); + } + + internal void FeedAndExpectLastRouterSet( + (IActorRef actor, TestProbe probe) autSet, string mode, + IReadOnlyList commands, int numExpect) + { + commands.ForEach(c => autSet.actor.Tell(new Broadcast(new Cmd(mode, c)))); + + for (var i = 0; i < numExpect; i++) + //Output.WriteLine("Expecting " + i); + autSet.probe.ExpectMsg(commands.Last(), ExpectDuration); + } + + internal void FeedAndExpectLast(IActorRef actor, string mode, IReadOnlyList commands) + { + commands.ForEach(c => actor.Tell(new Cmd(mode, c))); + testProbe.ExpectMsg(commands.Last(), ExpectDuration); + } + + internal void FeedAndExpectLastGroup( + (IActorRef actor, TestProbe probe)[] autSet, string mode, + IReadOnlyList commands) + { + foreach (var aut in autSet) commands.ForEach(c => aut.actor.Tell(new Cmd(mode, c))); + + foreach (var aut in autSet) aut.probe.ExpectMsg(commands.Last(), ExpectDuration); + } + + internal void FeedAndExpectLastSpecific((IActorRef actor, TestProbe probe) aut, string mode, + IReadOnlyList commands) + { + commands.ForEach(c => aut.actor.Tell(new Cmd(mode, c))); + + aut.probe.ExpectMsg(commands.Last(), ExpectDuration); + } + + internal void Measure(Func msg, Action block) + { + var measurements = new List(MeasurementIterations); + + block(); //warm-up + + var i = 0; + while (i < MeasurementIterations) + { + var sw = Stopwatch.StartNew(); + block(); + sw.Stop(); + measurements.Add(sw.Elapsed); + Output.WriteLine(msg(sw.Elapsed)); + i++; + } + + var avgTime = measurements.Select(c => c.TotalMilliseconds).Sum() / MeasurementIterations; + var msgPerSec = EventsCount / avgTime * 1000; + + Output.WriteLine($"Average time: {avgTime} ms, {msgPerSec} msg/sec"); + } + + internal void MeasureGroup(Func msg, Action block, int numMsg, int numGroup) + { + var measurements = new List(MeasurementIterations); + + block(); //warm-up + + var i = 0; + while (i < MeasurementIterations) + { + var sw = Stopwatch.StartNew(); + block(); + sw.Stop(); + measurements.Add(sw.Elapsed); + Output.WriteLine(msg(sw.Elapsed)); + i++; + } + + var avgTime = measurements.Select(c => c.TotalMilliseconds).Sum() / MeasurementIterations; + var msgPerSec = numMsg / avgTime * 1000; + var msgPerSecTotal = numMsg * numGroup / avgTime * 1000; + Output.WriteLine( + $"Workers: {numGroup} , Average time: {avgTime} ms, {msgPerSec} msg/sec/actor, {msgPerSecTotal} total msg/sec."); + } + + [DotMemoryUnit(CollectAllocations = true, FailIfRunWithoutSupport = false)] + [Fact] + public void DotMemory_PersistenceActor_performance_must_measure_Persist() + { + dotMemory.Check(); + + var p1 = BenchActor("DotMemoryPersistPid", EventsCount); + + dotMemory.Check((mem) => + { + Measure( + d => + $"Persist()-ing {EventsCount} took {d.TotalMilliseconds} ms", + () => + { + FeedAndExpectLast(p1, "p", Commands); + p1.Tell(ResetCounter.Instance); + }); + } + ); + dotMemory.Check((mem) => + { + Measure( + d => + $"Persist()-ing {EventsCount} took {d.TotalMilliseconds} ms", + () => + { + FeedAndExpectLast(p1, "p", Commands); + p1.Tell(ResetCounter.Instance); + }); + } + ); + dotMemoryApi.SaveCollectedData(@"c:\temp\dotmemory"); + } + + [DotMemoryUnit(CollectAllocations = true, FailIfRunWithoutSupport = false)] + [Fact] + public void DotMemory_PersistenceActor_performance_must_measure_PersistGroup400() + { + dotMemory.Check(); + + var numGroup = 400; + var numCommands = Math.Min(EventsCount / 100, 500); + + + dotMemory.Check((mem) => { RunGroupBenchmark(numGroup, numCommands); } + ); + dotMemory.Check((mem) => { RunGroupBenchmark(numGroup, numCommands); } + ); + dotMemoryApi.SaveCollectedData(@"c:\temp\dotmemory"); + } + + + [Fact] + public void PersistenceActor_performance_must_measure_Persist() + { + var p1 = BenchActor("PersistPid", EventsCount); + + //dotMemory.Check((mem) => + //{ + Measure( + d => + $"Persist()-ing {EventsCount} took {d.TotalMilliseconds} ms", + () => + { + FeedAndExpectLast(p1, "p", Commands); + p1.Tell(ResetCounter.Instance); + }); + //} + //); + //dotMemoryApi.SaveCollectedData(@"c:\temp\dotmemory"); + } + + [Fact] + public void PersistenceActor_performance_must_measure_PersistGroup10() + { + var numGroup = 10; + var numCommands = Math.Min(EventsCount / 10, 1000); + RunGroupBenchmark(numGroup, numCommands); + } + + [Fact] + public void PersistenceActor_performance_must_measure_PersistGroup25() + { + var numGroup = 25; + var numCommands = Math.Min(EventsCount / 25, 1000); + RunGroupBenchmark(numGroup, numCommands); + } + + [Fact] + public void PersistenceActor_performance_must_measure_PersistGroup50() + { + var numGroup = 50; + var numCommands = Math.Min(EventsCount / 50, 1000); + RunGroupBenchmark(numGroup, numCommands); + } + + [Fact] + public void PersistenceActor_performance_must_measure_PersistGroup100() + { + var numGroup = 100; + var numCommands = Math.Min(EventsCount / 100, 1000); + RunGroupBenchmark(numGroup, numCommands); + } + + [Fact] + public void PersistenceActor_performance_must_measure_PersistGroup200() + { + var numGroup = 200; + var numCommands = Math.Min(EventsCount / 100, 500); + RunGroupBenchmark(numGroup, numCommands); + } + + [Fact] + public void PersistenceActor_performance_must_measure_PersistGroup400() + { + var numGroup = 400; + var numCommands = Math.Min(EventsCount / 100, 500); + RunGroupBenchmark(numGroup, numCommands); + } + + protected void RunGroupBenchmark(int numGroup, int numCommands) + { + var p1 = BenchActorNewProbeGroup("GroupPersistPid" + numGroup, numGroup, + numCommands); + MeasureGroup( + d => + $"Persist()-ing {numCommands} * {numGroup} took {d.TotalMilliseconds} ms", + () => + { + FeedAndExpectLastRouterSet(p1, "p", + Commands.Take(numCommands).ToImmutableList(), + numGroup); + p1.aut.Tell(new Broadcast(ResetCounter.Instance)); + }, numCommands, numGroup + ); + } + + [Fact] + public void PersistenceActor_performance_must_measure_PersistAll() + { + var p1 = BenchActor("PersistAllPid", EventsCount); + Measure(d => $"PersistAll()-ing {EventsCount} took {d.TotalMilliseconds} ms", () => + { + FeedAndExpectLast(p1, "pb", Commands); + p1.Tell(ResetCounter.Instance); + }); + } + + [Fact] + public void PersistenceActor_performance_must_measure_PersistAsync() + { + var p1 = BenchActor("PersistAsyncPid", EventsCount); + Measure(d => $"PersistAsync()-ing {EventsCount} took {d.TotalMilliseconds} ms", () => + { + FeedAndExpectLast(p1, "pa", Commands); + p1.Tell(ResetCounter.Instance); + }); + } + + [Fact] + public void PersistenceActor_performance_must_measure_PersistAllAsync() + { + var p1 = BenchActor("PersistAllAsyncPid", EventsCount); + Measure(d => $"PersistAllAsync()-ing {EventsCount} took {d.TotalMilliseconds} ms", () => + { + FeedAndExpectLast(p1, "pba", Commands); + p1.Tell(ResetCounter.Instance); + }); + } + + [Fact] + public void PersistenceActor_performance_must_measure_Recovering() + { + var p1 = BenchActor("PersistRecoverPid", EventsCount); + + FeedAndExpectLast(p1, "p", Commands); + Measure(d => $"Recovering {EventsCount} took {d.TotalMilliseconds} ms", () => + { + BenchActor("PersistRecoverPid", EventsCount); + testProbe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + } + + [Fact] + public void PersistenceActor_performance_must_measure_RecoveringTwo() + { + var p1 = BenchActorNewProbe("DoublePersistRecoverPid1", EventsCount); + var p2 = BenchActorNewProbe("DoublePersistRecoverPid2", EventsCount); + FeedAndExpectLastSpecific(p1, "p", Commands); + FeedAndExpectLastSpecific(p2, "p", Commands); + MeasureGroup(d => $"Recovering {EventsCount} took {d.TotalMilliseconds} ms", () => + { + var task1 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("DoublePersistRecoverPid1", + EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task2 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("DoublePersistRecoverPid2", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + Task.WaitAll(new[] {task1, task2}); + }, EventsCount, 2); + } + + [Fact] + public void PersistenceActor_performance_must_measure_RecoveringFour() + { + var p1 = BenchActorNewProbe("QuadPersistRecoverPid1", EventsCount); + var p2 = BenchActorNewProbe("QuadPersistRecoverPid2", EventsCount); + var p3 = BenchActorNewProbe("QuadPersistRecoverPid3", EventsCount); + var p4 = BenchActorNewProbe("QuadPersistRecoverPid4", EventsCount); + FeedAndExpectLastSpecific(p1, "p", Commands); + FeedAndExpectLastSpecific(p2, "p", Commands); + FeedAndExpectLastSpecific(p3, "p", Commands); + FeedAndExpectLastSpecific(p4, "p", Commands); + MeasureGroup(d => $"Recovering {EventsCount} took {d.TotalMilliseconds} ms", () => + { + var task1 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("QuadPersistRecoverPid1", + EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task2 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("QuadPersistRecoverPid2", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task3 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("QuadPersistRecoverPid3", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task4 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("QuadPersistRecoverPid4", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + Task.WaitAll(new[] {task1, task2, task3, task4}); + }, EventsCount, 4); + } + + [Fact] + public void PersistenceActor_performance_must_measure_Recovering8() + { + var p1 = BenchActorNewProbe("OctPersistRecoverPid1", EventsCount); + var p2 = BenchActorNewProbe("OctPersistRecoverPid2", EventsCount); + var p3 = BenchActorNewProbe("OctPersistRecoverPid3", EventsCount); + var p4 = BenchActorNewProbe("OctPersistRecoverPid4", EventsCount); + var p5 = BenchActorNewProbe("OctPersistRecoverPid5", EventsCount); + var p6 = BenchActorNewProbe("OctPersistRecoverPid6", EventsCount); + var p7 = BenchActorNewProbe("OctPersistRecoverPid7", EventsCount); + var p8 = BenchActorNewProbe("OctPersistRecoverPid8", EventsCount); + FeedAndExpectLastSpecific(p1, "p", Commands); + FeedAndExpectLastSpecific(p2, "p", Commands); + FeedAndExpectLastSpecific(p3, "p", Commands); + FeedAndExpectLastSpecific(p4, "p", Commands); + FeedAndExpectLastSpecific(p5, "p", Commands); + FeedAndExpectLastSpecific(p6, "p", Commands); + FeedAndExpectLastSpecific(p7, "p", Commands); + FeedAndExpectLastSpecific(p8, "p", Commands); + MeasureGroup(d => $"Recovering {EventsCount} took {d.TotalMilliseconds} ms", () => + { + var task1 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("OctPersistRecoverPid1", + EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task2 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("OctPersistRecoverPid2", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task3 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("OctPersistRecoverPid3", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task4 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("OctPersistRecoverPid4", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task5 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("OctPersistRecoverPid5", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task6 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("OctPersistRecoverPid6", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task7 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("OctPersistRecoverPid7", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + var task8 = Task.Run(() => + { + var refAndProbe = BenchActorNewProbe("OctPersistRecoverPid8", EventsCount); + refAndProbe.probe.ExpectMsg(Commands.Last(), ExpectDuration); + }); + Task.WaitAll(new[] {task1, task2, task3, task4, task5, task6, task7, task8}); + }, EventsCount, 8); + } + } + + internal class ResetCounter + { + public static ResetCounter Instance { get; } = new ResetCounter(); + + private ResetCounter() + { + } + } + + public class Cmd + { + public Cmd(string mode, int payload) + { + Mode = mode; + Payload = payload; + } + + public string Mode { get; } + + public int Payload { get; } + } + + internal class BenchActor : UntypedPersistentActor + { + private int _counter = 0; + private const int BatchSize = 50; + private List _batch = new List(BatchSize); + + public BenchActor(string persistenceId, IActorRef replyTo, int replyAfter, bool groupName) + { + PersistenceId = persistenceId + MurmurHash.StringHash(Context.Parent.Path.Name + Context.Self.Path.Name); + ReplyTo = replyTo; + ReplyAfter = replyAfter; + } + + public BenchActor(string persistenceId, IActorRef replyTo, int replyAfter) + { + PersistenceId = persistenceId; + ReplyTo = replyTo; + ReplyAfter = replyAfter; + } + + public override string PersistenceId { get; } + public IActorRef ReplyTo { get; } + public int ReplyAfter { get; } + + protected override void OnRecover(object message) + { + switch (message) + { + case Cmd c: + _counter++; + if (c.Payload != _counter) + throw new ArgumentException($"Expected to receive [{_counter}] yet got: [{c.Payload}]"); + if (_counter == ReplyAfter) ReplyTo.Tell(c.Payload); + break; + } + } + + protected override void OnCommand(object message) + { + switch (message) + { + case Cmd c when c.Mode == "p": + Persist(c, d => + { + _counter += 1; + if (d.Payload != _counter) + throw new ArgumentException($"Expected to receive [{_counter}] yet got: [{d.Payload}]"); + if (_counter == ReplyAfter) ReplyTo.Tell(d.Payload); + }); + break; + case Cmd c when c.Mode == "pb": + _batch.Add(c); + + if (_batch.Count % BatchSize == 0) + { + PersistAll(_batch, d => + { + _counter += 1; + if (d.Payload != _counter) + throw new ArgumentException($"Expected to receive [{_counter}] yet got: [{d.Payload}]"); + if (_counter == ReplyAfter) ReplyTo.Tell(d.Payload); + }); + _batch = new List(BatchSize); + } + + break; + case Cmd c when c.Mode == "pa": + PersistAsync(c, d => + { + _counter += 1; + if (d.Payload != _counter) + throw new ArgumentException($"Expected to receive [{_counter}] yet got: [{d.Payload}]"); + if (_counter == ReplyAfter) ReplyTo.Tell(d.Payload); + }); + break; + case Cmd c when c.Mode == "pba": + _batch.Add(c); + + if (_batch.Count % BatchSize == 0) + { + PersistAllAsync(_batch, d => + { + _counter += 1; + if (d.Payload != _counter) + throw new ArgumentException($"Expected to receive [{_counter}] yet got: [{d.Payload}]"); + if (_counter == ReplyAfter) ReplyTo.Tell(d.Payload); + }); + _batch = new List(BatchSize); + } + + break; + case ResetCounter _: + _counter = 0; + break; + } + } + } +} \ No newline at end of file diff --git a/src/common.props b/src/common.props index f813854..4bfc9fe 100644 --- a/src/common.props +++ b/src/common.props @@ -1,24 +1,53 @@ - Copyright © 2013-2020 Akka.NET Project + Copyright © 2013-2021 Akka.NET Project Akka.NET - 1.4.4 + 1.4.16 akka;actors;actor model;Akka;concurrency;persistence;eventsource;redis http://getakka.net/images/AkkaNetLogo.Normal.png - https://github.com/AkkaNetContrib/Akka.Persistence.Redis - https://github.com/AkkaNetContrib/Akka.Persistence.Redis/blob/dev/LICENSE - - Bump Akka to version 1.4.4 -- Update build system to use Docker.DotNet + https://github.com/akkadotnet/Akka.Persistence.Redis + https://github.com/akkadotnet/Akka.Persistence.Redis/blob/dev/LICENSE + This is a major update to the Akka.Persistence.Redis plugin. +Enabled Redis Cluster Support** +Akka.Persistence.Redis will now automatically detect whether or not you are running in clustered mode via your Redis connection string and will distribute journal entries and snapshots accordingly. +All journal entries and all snapshots for a single entity will all reside inside the same Redis host cost - [using Redis' consistent hash distribution tagging](https://redis.io/topics/cluster-tutorial) scheme. +Significant Performance Improvements** +Akka.Persistence.Redis' write throughput was improved significantly in Akka.Persistence.Redis v1.4.16: +| Test | Akka.Persistence.Redis v1.4.4 (msg/s) | current PR (msg/s) | +|-----------------|---------------------------------------|--------------------| +| Persist | 782 | 772 | +| PersistAll | 15019 | 20275 | +| PersistAsync | 9496 | 13131 | +| PersistAllAsync | 32765 | 44776 | +| PersistGroup10 | 611 | 6523 | +| PersistGroup100 | 8878 | 12533 | +| PersistGroup200 | 9598 | 12214 | +| PersistGroup25 | 9209 | 10819 | +| PersistGroup400 | 9209 | 11824 | +| PersistGroup50 | 9506 | 9704 | +| Recovering | 17374 | 20119 | +| Recovering8 | 36915 | 37290 | +| RecoveringFour | 22432 | 20884 | +| RecoveringTwo | 22209 | 21222 | +These numbers were generated running a single Redis instance inside a Docker container on Docker for Windows - real-world values generated in cloud environments will likely be much higher. +Removed Akka.Persistence.Query Support** +In order to achieve support for clustering and improved write performance, we made the descision to drop Akka.Persistence.Query support from Akka.Persistence.Redis at this time - if you wish to learn more about our decision-making process or if you are affected by this change, please comment on this thread here: https://github.com/akkadotnet/Akka.Persistence.Redis/issues/126 +Other Changes** +- Bump [Akka.NET to version 1.4.16](https://github.com/akkadotnet/akka.net/releases/tag/1.4.16) +- Modernized Akka.NET Serialization calls +- [Added benchmarks](https://github.com/akkadotnet/Akka.Persistence.Redis/pull/118) +- Upgraded to [StackExchange.Redis 2.2.11](https://github.com/StackExchange/StackExchange.Redis/blob/main/docs/ReleaseNotes.md) +- Improved documentation true Akka.NET Persistence journal and snapshot store backed by Redis. $(NoWarn);CS1591 2.4.1 - 16.5.0 - 1.4.4 - 2.1.30 - 3.125.2 + 16.8.3 + 1.4.16 + 2.2.11 + 3.125.4 5.10.3 netcoreapp3.1 netstandard2.0 diff --git a/src/examples/CustomSerialization.MsgPack/App.config b/src/examples/CustomSerialization.MsgPack/App.config index a6ad828..ae0b09c 100644 --- a/src/examples/CustomSerialization.MsgPack/App.config +++ b/src/examples/CustomSerialization.MsgPack/App.config @@ -1,6 +1,7 @@ - + + - + \ No newline at end of file diff --git a/src/examples/CustomSerialization.MsgPack/CustomSerialization.MsgPack.csproj b/src/examples/CustomSerialization.MsgPack/CustomSerialization.MsgPack.csproj index 4df47ec..b369f19 100644 --- a/src/examples/CustomSerialization.MsgPack/CustomSerialization.MsgPack.csproj +++ b/src/examples/CustomSerialization.MsgPack/CustomSerialization.MsgPack.csproj @@ -5,6 +5,7 @@ Exe $(NetCoreTestVersion) true + false @@ -16,7 +17,7 @@ - + diff --git a/src/examples/CustomSerialization.MsgPack/Messages.cs b/src/examples/CustomSerialization.MsgPack/Messages.cs index 9e5339d..bc6e716 100644 --- a/src/examples/CustomSerialization.MsgPack/Messages.cs +++ b/src/examples/CustomSerialization.MsgPack/Messages.cs @@ -1,9 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2009-2016 Lightbend Inc. -// Copyright (C) 2013-2016 Akka.NET project +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using System; using System.Runtime.Serialization; @@ -13,7 +12,10 @@ namespace CustomSerialization.MsgPack public sealed class StopMeasure { public static StopMeasure Instance { get; } = new StopMeasure(); - private StopMeasure() { } + + private StopMeasure() + { + } } public sealed class FailAt diff --git a/src/examples/CustomSerialization.MsgPack/PerformanceActors.cs b/src/examples/CustomSerialization.MsgPack/PerformanceActors.cs index b09db15..c4e5ff1 100644 --- a/src/examples/CustomSerialization.MsgPack/PerformanceActors.cs +++ b/src/examples/CustomSerialization.MsgPack/PerformanceActors.cs @@ -1,9 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2009-2016 Lightbend Inc. -// Copyright (C) 2013-2016 Akka.NET project +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using Akka; using Akka.Actor; @@ -15,19 +14,28 @@ namespace CustomSerialization.MsgPack public sealed class Init { public static Init Instance { get; } = new Init(); - private Init() { } + + private Init() + { + } } public sealed class Finish { public static Finish Instance { get; } = new Finish(); - private Finish() { } + + private Finish() + { + } } public sealed class Done { public static Done Instance { get; } = new Done(); - private Done() { } + + private Done() + { + } } public sealed class Finished @@ -59,8 +67,7 @@ public Stored(int value) Value = value; } - [Key(0)] - public int Value { get; } + [Key(0)] public int Value { get; } } public class PerformanceTestActor : UntypedPersistentActor @@ -87,10 +94,7 @@ protected override void OnCommand(object message) }); break; case Store store: - Persist(new Stored(store.Value), s => - { - _state += s.Value; - }); + Persist(new Stored(store.Value), s => { _state += s.Value; }); break; case Finish _: Sender.Tell(new Finished(_state)); diff --git a/src/examples/CustomSerialization.MsgPack/Program.cs b/src/examples/CustomSerialization.MsgPack/Program.cs index 486cdbf..4e3a512 100644 --- a/src/examples/CustomSerialization.MsgPack/Program.cs +++ b/src/examples/CustomSerialization.MsgPack/Program.cs @@ -1,9 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2009-2016 Lightbend Inc. -// Copyright (C) 2013-2016 Akka.NET project +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using System; using System.Diagnostics; @@ -15,7 +14,7 @@ namespace CustomSerialization.MsgPack { - class Program + internal class Program { // if you want to benchmark your persistent storage provides, paste the configuration in string below // by default we're checking against in-memory journal @@ -39,14 +38,15 @@ class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" public const int ActorCount = 1000; public const int MessagesPerActor = 100; - static void Main(string[] args) + private static void Main(string[] args) { - using (var system = ActorSystem.Create("persistent-benchmark", config.WithFallback(ConfigurationFactory.Default()))) + using (var system = ActorSystem.Create("persistent-benchmark", + config.WithFallback(ConfigurationFactory.Default()))) { Console.WriteLine("Performance benchmark starting..."); var actors = new IActorRef[ActorCount]; - for (int i = 0; i < ActorCount; i++) + for (var i = 0; i < ActorCount; i++) { var pid = "a-" + i; actors[i] = system.ActorOf(Props.Create(() => new PerformanceTestActor(pid))); @@ -59,34 +59,26 @@ static void Main(string[] args) var stopwatch = new Stopwatch(); stopwatch.Start(); - for (int i = 0; i < MessagesPerActor; i++) - { - for (int j = 0; j < ActorCount; j++) - { - actors[j].Tell(new Store(1)); - } - } + for (var i = 0; i < MessagesPerActor; i++) + for (var j = 0; j < ActorCount; j++) + actors[j].Tell(new Store(1)); var finished = new Task[ActorCount]; - for (int i = 0; i < ActorCount; i++) - { - finished[i] = actors[i].Ask(Finish.Instance); - } + for (var i = 0; i < ActorCount; i++) finished[i] = actors[i].Ask(Finish.Instance); Task.WaitAll(finished); var elapsed = stopwatch.ElapsedMilliseconds; - Console.WriteLine($"{ActorCount} actors stored {MessagesPerActor} events each in {elapsed/1000.0} sec. Average: {ActorCount*MessagesPerActor*1000.0/elapsed} events/sec"); + Console.WriteLine( + $"{ActorCount} actors stored {MessagesPerActor} events each in {elapsed / 1000.0} sec. Average: {ActorCount * MessagesPerActor * 1000.0 / elapsed} events/sec"); foreach (Task task in finished) - { if (!task.IsCompleted || task.Result.State != MessagesPerActor) throw new IllegalStateException("Actor's state was invalid"); - } } Console.ReadLine(); } } -} +} \ No newline at end of file diff --git a/src/examples/CustomSerialization.MsgPack/Serialization/ActorPathResolver.cs b/src/examples/CustomSerialization.MsgPack/Serialization/ActorPathResolver.cs index cca0b15..5fcb6b7 100644 --- a/src/examples/CustomSerialization.MsgPack/Serialization/ActorPathResolver.cs +++ b/src/examples/CustomSerialization.MsgPack/Serialization/ActorPathResolver.cs @@ -1,9 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2009-2016 Lightbend Inc. -// Copyright (C) 2013-2016 Akka.NET project +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using System; using System.Collections.Generic; @@ -16,13 +15,24 @@ namespace CustomSerialization.MsgPack.Serialization public class ActorPathResolver : IFormatterResolver { public static IFormatterResolver Instance = new ActorPathResolver(); - private ActorPathResolver() { } - public IMessagePackFormatter GetFormatter() => FormatterCache.Formatter; + + private ActorPathResolver() + { + } + + public IMessagePackFormatter GetFormatter() + { + return FormatterCache.Formatter; + } private static class FormatterCache { public static readonly IMessagePackFormatter Formatter; - static FormatterCache() => Formatter = (IMessagePackFormatter)ActorPathResolverGetFormatterHelper.GetFormatter(typeof(T)); + + static FormatterCache() + { + Formatter = (IMessagePackFormatter) ActorPathResolverGetFormatterHelper.GetFormatter(typeof(T)); + } } } @@ -35,33 +45,32 @@ internal static class ActorPathResolverGetFormatterHelper {typeof(RootActorPath), new ActorPathFormatter()} }; - internal static object GetFormatter(Type t) => FormatterMap.TryGetValue(t, out var formatter) ? formatter : null; + internal static object GetFormatter(Type t) + { + return FormatterMap.TryGetValue(t, out var formatter) ? formatter : null; + } } public class ActorPathFormatter : IMessagePackFormatter where T : ActorPath { - public int Serialize(ref byte[] bytes, int offset, T value, IFormatterResolver formatterResolver) + public void Serialize(ref MessagePackWriter writer, T value, MessagePackSerializerOptions options) { if (value == null) { - return MessagePackBinary.WriteNil(ref bytes, offset); + writer.WriteNil(); + + return; } - var startOffset = offset; - offset += MessagePackBinary.WriteString(ref bytes, offset, value.ToSerializationFormat()); - return offset - startOffset; + writer.Write(value.ToSerializationFormat()); } - public T Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize) + public T Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { - if (MessagePackBinary.IsNil(bytes, offset)) - { - readSize = 1; - return null; - } + if (reader.IsNil) return null; - var path = MessagePackBinary.ReadString(bytes, offset, out readSize); - return ActorPath.TryParse(path, out var actorPath) ? (T)actorPath : null; + var path = reader.ReadString(); + return ActorPath.TryParse(path, out var actorPath) ? (T) actorPath : null; } } -} +} \ No newline at end of file diff --git a/src/examples/CustomSerialization.MsgPack/Serialization/MsgPackSerializer.cs b/src/examples/CustomSerialization.MsgPack/Serialization/MsgPackSerializer.cs index 4c74b25..dc86f75 100644 --- a/src/examples/CustomSerialization.MsgPack/Serialization/MsgPackSerializer.cs +++ b/src/examples/CustomSerialization.MsgPack/Serialization/MsgPackSerializer.cs @@ -1,9 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2009-2016 Lightbend Inc. -// Copyright (C) 2013-2016 Akka.NET project +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using System; using Akka.Actor; @@ -18,11 +17,13 @@ namespace CustomSerialization.MsgPack.Serialization public class MsgPackSerializer : Serializer { #region Messages + [MessagePackObject] public class PersistenceMessage { [SerializationConstructor] - public PersistenceMessage(string persistenceId, long sequenceNr, string writerGuid, int serializerId, string manifest, byte[] payload) + public PersistenceMessage(string persistenceId, long sequenceNr, string writerGuid, int serializerId, + string manifest, byte[] payload) { PersistenceId = persistenceId; SequenceNr = sequenceNr; @@ -32,32 +33,24 @@ public PersistenceMessage(string persistenceId, long sequenceNr, string writerGu Payload = payload; } - [Key(0)] - public string PersistenceId { get; } + [Key(0)] public string PersistenceId { get; } - [Key(1)] - public long SequenceNr { get; } + [Key(1)] public long SequenceNr { get; } - [Key(2)] - public string WriterGuid { get; } + [Key(2)] public string WriterGuid { get; } - [Key(3)] - public int SerializerId { get; } + [Key(3)] public int SerializerId { get; } - [Key(4)] - public string Manifest { get; } + [Key(4)] public string Manifest { get; } - [Key(5)] - public byte[] Payload { get; } + [Key(5)] public byte[] Payload { get; } } + #endregion static MsgPackSerializer() { - CompositeResolver.RegisterAndSetAsDefault( - ActorPathResolver.Instance, - OldSpecResolver.Instance, // Redis compatible MsgPack spec - ContractlessStandardResolver.Instance); + CompositeResolver.Create(new[] {ActorPathResolver.Instance}); } public MsgPackSerializer(ExtendedActorSystem system) : base(system) @@ -69,7 +62,7 @@ public override byte[] ToBinary(object obj) if (obj is IPersistentRepresentation repr) return PersistenceMessageSerializer(repr); - return MessagePackSerializer.NonGeneric.Serialize(obj.GetType(), obj); + return MessagePackSerializer.Serialize(obj.GetType(), obj); } public override object FromBinary(byte[] bytes, Type type) @@ -77,7 +70,7 @@ public override object FromBinary(byte[] bytes, Type type) if (typeof(IPersistentRepresentation).IsAssignableFrom(type)) return PersistenceMessageDeserializer(bytes); - return MessagePackSerializer.NonGeneric.Deserialize(type, bytes); + return MessagePackSerializer.Deserialize(type, bytes); } public override int Identifier => 30; @@ -134,4 +127,4 @@ private IPersistentRepresentation PersistenceMessageDeserializer(byte[] bytes) persistenceMessage.WriterGuid); } } -} +} \ No newline at end of file diff --git a/src/examples/CustomSerialization.Protobuf/App.config b/src/examples/CustomSerialization.Protobuf/App.config index a6ad828..ae0b09c 100644 --- a/src/examples/CustomSerialization.Protobuf/App.config +++ b/src/examples/CustomSerialization.Protobuf/App.config @@ -1,6 +1,7 @@ - + + - + \ No newline at end of file diff --git a/src/examples/CustomSerialization.Protobuf/CustomSerialization.Protobuf.csproj b/src/examples/CustomSerialization.Protobuf/CustomSerialization.Protobuf.csproj index e77d083..0a143b7 100644 --- a/src/examples/CustomSerialization.Protobuf/CustomSerialization.Protobuf.csproj +++ b/src/examples/CustomSerialization.Protobuf/CustomSerialization.Protobuf.csproj @@ -5,6 +5,7 @@ Exe $(NetCoreTestVersion) true + false diff --git a/src/examples/CustomSerialization.Protobuf/Messages.cs b/src/examples/CustomSerialization.Protobuf/Messages.cs index ccd4767..2ed803b 100644 --- a/src/examples/CustomSerialization.Protobuf/Messages.cs +++ b/src/examples/CustomSerialization.Protobuf/Messages.cs @@ -1,9 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2009-2016 Lightbend Inc. -// Copyright (C) 2013-2016 Akka.NET project +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using System; using System.Runtime.Serialization; @@ -13,7 +12,10 @@ namespace CustomSerialization.Protobuf public sealed class StopMeasure { public static StopMeasure Instance { get; } = new StopMeasure(); - private StopMeasure() {} + + private StopMeasure() + { + } } public sealed class FailAt @@ -46,7 +48,7 @@ public void StartMeasure() public double StopMeasure() { StopedAt = DateTime.Now; - return MessagesCount/(StopedAt - StartedAt).TotalSeconds; + return MessagesCount / (StopedAt - StartedAt).TotalSeconds; } } diff --git a/src/examples/CustomSerialization.Protobuf/PerformanceActors.cs b/src/examples/CustomSerialization.Protobuf/PerformanceActors.cs index 3221f45..f93963b 100644 --- a/src/examples/CustomSerialization.Protobuf/PerformanceActors.cs +++ b/src/examples/CustomSerialization.Protobuf/PerformanceActors.cs @@ -1,9 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2009-2016 Lightbend Inc. -// Copyright (C) 2013-2016 Akka.NET project +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using Akka.Actor; using Akka.Persistence; @@ -13,19 +12,28 @@ namespace CustomSerialization.Protobuf public sealed class Init { public static Init Instance { get; } = new Init(); - private Init() { } + + private Init() + { + } } public sealed class Finish { public static Finish Instance { get; } = new Finish(); - private Finish() { } + + private Finish() + { + } } public sealed class Done { public static Done Instance { get; } = new Done(); - private Done() { } + + private Done() + { + } } public sealed class Finished @@ -82,10 +90,7 @@ protected override void OnCommand(object message) }); break; case Store store: - Persist(new Stored(store.Value), s => - { - _state += s.Value; - }); + Persist(new Stored(store.Value), s => { _state += s.Value; }); break; case Finish _: Sender.Tell(new Finished(_state)); diff --git a/src/examples/CustomSerialization.Protobuf/Program.cs b/src/examples/CustomSerialization.Protobuf/Program.cs index 9d68c13..275503f 100644 --- a/src/examples/CustomSerialization.Protobuf/Program.cs +++ b/src/examples/CustomSerialization.Protobuf/Program.cs @@ -1,9 +1,8 @@ -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- // -// Copyright (C) 2009-2016 Lightbend Inc. -// Copyright (C) 2013-2016 Akka.NET project +// Copyright (C) 2013-2021 .NET Foundation // -//----------------------------------------------------------------------- +// ----------------------------------------------------------------------- using System; using System.Diagnostics; @@ -15,7 +14,7 @@ namespace CustomSerialization.Protobuf { - class Program + internal class Program { // if you want to benchmark your persistent storage provides, paste the configuration in string below // by default we're checking against in-memory journal @@ -39,14 +38,15 @@ class = ""Akka.Persistence.Redis.Journal.RedisJournal, Akka.Persistence.Redis"" public const int ActorCount = 1000; public const int MessagesPerActor = 100; - static void Main(string[] args) + private static void Main(string[] args) { - using (var system = ActorSystem.Create("persistent-benchmark", config.WithFallback(ConfigurationFactory.Default()))) + using (var system = ActorSystem.Create("persistent-benchmark", + config.WithFallback(ConfigurationFactory.Default()))) { Console.WriteLine("Performance benchmark starting..."); var actors = new IActorRef[ActorCount]; - for (int i = 0; i < ActorCount; i++) + for (var i = 0; i < ActorCount; i++) { var pid = "a-" + i; actors[i] = system.ActorOf(Props.Create(() => new PerformanceTestActor(pid))); @@ -59,34 +59,26 @@ static void Main(string[] args) var stopwatch = new Stopwatch(); stopwatch.Start(); - for (int i = 0; i < MessagesPerActor; i++) - { - for (int j = 0; j < ActorCount; j++) - { - actors[j].Tell(new Store(1)); - } - } + for (var i = 0; i < MessagesPerActor; i++) + for (var j = 0; j < ActorCount; j++) + actors[j].Tell(new Store(1)); var finished = new Task[ActorCount]; - for (int i = 0; i < ActorCount; i++) - { - finished[i] = actors[i].Ask(Finish.Instance); - } + for (var i = 0; i < ActorCount; i++) finished[i] = actors[i].Ask(Finish.Instance); Task.WaitAll(finished); var elapsed = stopwatch.ElapsedMilliseconds; - Console.WriteLine($"{ActorCount} actors stored {MessagesPerActor} events each in {elapsed/1000.0} sec. Average: {ActorCount*MessagesPerActor*1000.0/elapsed} events/sec"); + Console.WriteLine( + $"{ActorCount} actors stored {MessagesPerActor} events each in {elapsed / 1000.0} sec. Average: {ActorCount * MessagesPerActor * 1000.0 / elapsed} events/sec"); foreach (Task task in finished) - { if (!task.IsCompleted || task.Result.State != MessagesPerActor) throw new IllegalStateException("Actor's state was invalid"); - } } Console.ReadLine(); } } -} +} \ No newline at end of file diff --git a/src/examples/CustomSerialization.Protobuf/Serialization/MessageFormats.cs b/src/examples/CustomSerialization.Protobuf/Serialization/MessageFormats.cs index c51a6eb..20a8f16 100644 --- a/src/examples/CustomSerialization.Protobuf/Serialization/MessageFormats.cs +++ b/src/examples/CustomSerialization.Protobuf/Serialization/MessageFormats.cs @@ -1,546 +1,706 @@ -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: MessageFormats.proto +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + #pragma warning disable 1591, 0612, 3021 + #region Designer generated code using pb = global::Google.Protobuf; using pbc = global::Google.Protobuf.Collections; using pbr = global::Google.Protobuf.Reflection; using scg = global::System.Collections.Generic; -namespace CustomSerialization.Protobuf.Msg { - /// Holder for reflection information generated from MessageFormats.proto - public static partial class MessageFormatsReflection { +namespace CustomSerialization.Protobuf.Msg +{ + /// Holder for reflection information generated from MessageFormats.proto + public static partial class MessageFormatsReflection + { + #region Descriptor + + /// File descriptor for MessageFormats.proto + public static pbr::FileDescriptor Descriptor + { + get { return descriptor; } + } + + private static pbr::FileDescriptor descriptor; + + static MessageFormatsReflection() + { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "ChRNZXNzYWdlRm9ybWF0cy5wcm90bxIgQ3VzdG9tU2VyaWFsaXphdGlvbi5Q", + "cm90b2J1Zi5Nc2cimAEKEVBlcnNpc3RlbnRNZXNzYWdlEhUKDXBlcnNpc3Rl", + "bmNlSWQYASABKAkSEgoKc2VxdWVuY2VOchgCIAEoAxISCgp3cml0ZXJHdWlk", + "GAMgASgJEkQKB3BheWxvYWQYBCABKAsyMy5DdXN0b21TZXJpYWxpemF0aW9u", + "LlByb3RvYnVmLk1zZy5QZXJzaXN0ZW50UGF5bG9hZCJTChFQZXJzaXN0ZW50", + "UGF5bG9hZBIPCgdtZXNzYWdlGAEgASgMEhQKDHNlcmlhbGl6ZXJJZBgCIAEo", + "BRIXCg9tZXNzYWdlTWFuaWZlc3QYAyABKAwiFwoGU3RvcmVkEg0KBXZhbHVl", + "GAEgASgFYgZwcm90bzM=")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(null, + new pbr::GeneratedClrTypeInfo[] + { + new pbr::GeneratedClrTypeInfo( + typeof(global::CustomSerialization.Protobuf.Msg.PersistentMessage), + global::CustomSerialization.Protobuf.Msg.PersistentMessage.Parser, + new[] {"PersistenceId", "SequenceNr", "WriterGuid", "Payload"}, null, null, null), + new pbr::GeneratedClrTypeInfo( + typeof(global::CustomSerialization.Protobuf.Msg.PersistentPayload), + global::CustomSerialization.Protobuf.Msg.PersistentPayload.Parser, + new[] {"Message", "SerializerId", "MessageManifest"}, null, null, null), + new pbr::GeneratedClrTypeInfo(typeof(global::CustomSerialization.Protobuf.Msg.Stored), + global::CustomSerialization.Protobuf.Msg.Stored.Parser, new[] {"Value"}, null, null, + null) + })); + } - #region Descriptor - /// File descriptor for MessageFormats.proto - public static pbr::FileDescriptor Descriptor { - get { return descriptor; } - } - private static pbr::FileDescriptor descriptor; - - static MessageFormatsReflection() { - byte[] descriptorData = global::System.Convert.FromBase64String( - string.Concat( - "ChRNZXNzYWdlRm9ybWF0cy5wcm90bxIgQ3VzdG9tU2VyaWFsaXphdGlvbi5Q", - "cm90b2J1Zi5Nc2cimAEKEVBlcnNpc3RlbnRNZXNzYWdlEhUKDXBlcnNpc3Rl", - "bmNlSWQYASABKAkSEgoKc2VxdWVuY2VOchgCIAEoAxISCgp3cml0ZXJHdWlk", - "GAMgASgJEkQKB3BheWxvYWQYBCABKAsyMy5DdXN0b21TZXJpYWxpemF0aW9u", - "LlByb3RvYnVmLk1zZy5QZXJzaXN0ZW50UGF5bG9hZCJTChFQZXJzaXN0ZW50", - "UGF5bG9hZBIPCgdtZXNzYWdlGAEgASgMEhQKDHNlcmlhbGl6ZXJJZBgCIAEo", - "BRIXCg9tZXNzYWdlTWFuaWZlc3QYAyABKAwiFwoGU3RvcmVkEg0KBXZhbHVl", - "GAEgASgFYgZwcm90bzM=")); - descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, - new pbr::FileDescriptor[] { }, - new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] { - new pbr::GeneratedClrTypeInfo(typeof(global::CustomSerialization.Protobuf.Msg.PersistentMessage), global::CustomSerialization.Protobuf.Msg.PersistentMessage.Parser, new[]{ "PersistenceId", "SequenceNr", "WriterGuid", "Payload" }, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::CustomSerialization.Protobuf.Msg.PersistentPayload), global::CustomSerialization.Protobuf.Msg.PersistentPayload.Parser, new[]{ "Message", "SerializerId", "MessageManifest" }, null, null, null), - new pbr::GeneratedClrTypeInfo(typeof(global::CustomSerialization.Protobuf.Msg.Stored), global::CustomSerialization.Protobuf.Msg.Stored.Parser, new[]{ "Value" }, null, null, null) - })); + #endregion } - #endregion - } - #region Messages - public sealed partial class PersistentMessage : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new PersistentMessage()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + #region Messages - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::CustomSerialization.Protobuf.Msg.MessageFormatsReflection.Descriptor.MessageTypes[0]; } - } + public sealed partial class PersistentMessage : pb::IMessage + { + private static readonly pb::MessageParser _parser = + new pb::MessageParser(() => new PersistentMessage()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser + { + get { return _parser; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public PersistentMessage() { - OnConstruction(); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor + { + get { return global::CustomSerialization.Protobuf.Msg.MessageFormatsReflection.Descriptor.MessageTypes[0]; } + } - partial void OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public PersistentMessage(PersistentMessage other) : this() { - persistenceId_ = other.persistenceId_; - sequenceNr_ = other.sequenceNr_; - writerGuid_ = other.writerGuid_; - Payload = other.payload_ != null ? other.Payload.Clone() : null; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public PersistentMessage() + { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public PersistentMessage Clone() { - return new PersistentMessage(this); - } + partial void OnConstruction(); - /// Field number for the "persistenceId" field. - public const int PersistenceIdFieldNumber = 1; - private string persistenceId_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string PersistenceId { - get { return persistenceId_; } - set { - persistenceId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public PersistentMessage(PersistentMessage other) : this() + { + persistenceId_ = other.persistenceId_; + sequenceNr_ = other.sequenceNr_; + writerGuid_ = other.writerGuid_; + Payload = other.payload_ != null ? other.Payload.Clone() : null; + } - /// Field number for the "sequenceNr" field. - public const int SequenceNrFieldNumber = 2; - private long sequenceNr_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public long SequenceNr { - get { return sequenceNr_; } - set { - sequenceNr_ = value; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public PersistentMessage Clone() + { + return new PersistentMessage(this); + } - /// Field number for the "writerGuid" field. - public const int WriterGuidFieldNumber = 3; - private string writerGuid_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public string WriterGuid { - get { return writerGuid_; } - set { - writerGuid_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } + /// Field number for the "persistenceId" field. + public const int PersistenceIdFieldNumber = 1; - /// Field number for the "payload" field. - public const int PayloadFieldNumber = 4; - private global::CustomSerialization.Protobuf.Msg.PersistentPayload payload_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public global::CustomSerialization.Protobuf.Msg.PersistentPayload Payload { - get { return payload_; } - set { - payload_ = value; - } - } + private string persistenceId_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as PersistentMessage); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string PersistenceId + { + get { return persistenceId_; } + set { persistenceId_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(PersistentMessage other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (PersistenceId != other.PersistenceId) return false; - if (SequenceNr != other.SequenceNr) return false; - if (WriterGuid != other.WriterGuid) return false; - if (!object.Equals(Payload, other.Payload)) return false; - return true; - } + /// Field number for the "sequenceNr" field. + public const int SequenceNrFieldNumber = 2; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (PersistenceId.Length != 0) hash ^= PersistenceId.GetHashCode(); - if (SequenceNr != 0L) hash ^= SequenceNr.GetHashCode(); - if (WriterGuid.Length != 0) hash ^= WriterGuid.GetHashCode(); - if (payload_ != null) hash ^= Payload.GetHashCode(); - return hash; - } + private long sequenceNr_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public long SequenceNr + { + get { return sequenceNr_; } + set { sequenceNr_ = value; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (PersistenceId.Length != 0) { - output.WriteRawTag(10); - output.WriteString(PersistenceId); - } - if (SequenceNr != 0L) { - output.WriteRawTag(16); - output.WriteInt64(SequenceNr); - } - if (WriterGuid.Length != 0) { - output.WriteRawTag(26); - output.WriteString(WriterGuid); - } - if (payload_ != null) { - output.WriteRawTag(34); - output.WriteMessage(Payload); - } - } + /// Field number for the "writerGuid" field. + public const int WriterGuidFieldNumber = 3; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (PersistenceId.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(PersistenceId); - } - if (SequenceNr != 0L) { - size += 1 + pb::CodedOutputStream.ComputeInt64Size(SequenceNr); - } - if (WriterGuid.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeStringSize(WriterGuid); - } - if (payload_ != null) { - size += 1 + pb::CodedOutputStream.ComputeMessageSize(Payload); - } - return size; - } + private string writerGuid_ = ""; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(PersistentMessage other) { - if (other == null) { - return; - } - if (other.PersistenceId.Length != 0) { - PersistenceId = other.PersistenceId; - } - if (other.SequenceNr != 0L) { - SequenceNr = other.SequenceNr; - } - if (other.WriterGuid.Length != 0) { - WriterGuid = other.WriterGuid; - } - if (other.payload_ != null) { - if (payload_ == null) { - payload_ = new global::CustomSerialization.Protobuf.Msg.PersistentPayload(); - } - Payload.MergeFrom(other.Payload); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public string WriterGuid + { + get { return writerGuid_; } + set { writerGuid_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - input.SkipLastField(); - break; - case 10: { - PersistenceId = input.ReadString(); - break; - } - case 16: { - SequenceNr = input.ReadInt64(); - break; - } - case 26: { - WriterGuid = input.ReadString(); - break; - } - case 34: { - if (payload_ == null) { - payload_ = new global::CustomSerialization.Protobuf.Msg.PersistentPayload(); - } - input.ReadMessage(payload_); - break; - } - } - } - } + /// Field number for the "payload" field. + public const int PayloadFieldNumber = 4; - } + private global::CustomSerialization.Protobuf.Msg.PersistentPayload payload_; - public sealed partial class PersistentPayload : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new PersistentPayload()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public global::CustomSerialization.Protobuf.Msg.PersistentPayload Payload + { + get { return payload_; } + set { payload_ = value; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::CustomSerialization.Protobuf.Msg.MessageFormatsReflection.Descriptor.MessageTypes[1]; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) + { + return Equals(other as PersistentMessage); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(PersistentMessage other) + { + if (ReferenceEquals(other, null)) + { + return false; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public PersistentPayload() { - OnConstruction(); - } + if (ReferenceEquals(other, this)) + { + return true; + } - partial void OnConstruction(); + if (PersistenceId != other.PersistenceId) return false; + if (SequenceNr != other.SequenceNr) return false; + if (WriterGuid != other.WriterGuid) return false; + if (!object.Equals(Payload, other.Payload)) return false; + return true; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public PersistentPayload(PersistentPayload other) : this() { - message_ = other.message_; - serializerId_ = other.serializerId_; - messageManifest_ = other.messageManifest_; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() + { + int hash = 1; + if (PersistenceId.Length != 0) hash ^= PersistenceId.GetHashCode(); + if (SequenceNr != 0L) hash ^= SequenceNr.GetHashCode(); + if (WriterGuid.Length != 0) hash ^= WriterGuid.GetHashCode(); + if (payload_ != null) hash ^= Payload.GetHashCode(); + return hash; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public PersistentPayload Clone() { - return new PersistentPayload(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } - /// Field number for the "message" field. - public const int MessageFieldNumber = 1; - private pb::ByteString message_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pb::ByteString Message { - get { return message_; } - set { - message_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) + { + if (PersistenceId.Length != 0) + { + output.WriteRawTag(10); + output.WriteString(PersistenceId); + } - /// Field number for the "serializerId" field. - public const int SerializerIdFieldNumber = 2; - private int serializerId_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int SerializerId { - get { return serializerId_; } - set { - serializerId_ = value; - } - } + if (SequenceNr != 0L) + { + output.WriteRawTag(16); + output.WriteInt64(SequenceNr); + } - /// Field number for the "messageManifest" field. - public const int MessageManifestFieldNumber = 3; - private pb::ByteString messageManifest_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public pb::ByteString MessageManifest { - get { return messageManifest_; } - set { - messageManifest_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); - } - } + if (WriterGuid.Length != 0) + { + output.WriteRawTag(26); + output.WriteString(WriterGuid); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as PersistentPayload); - } + if (payload_ != null) + { + output.WriteRawTag(34); + output.WriteMessage(Payload); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(PersistentPayload other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (Message != other.Message) return false; - if (SerializerId != other.SerializerId) return false; - if (MessageManifest != other.MessageManifest) return false; - return true; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() + { + int size = 0; + if (PersistenceId.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(PersistenceId); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (Message.Length != 0) hash ^= Message.GetHashCode(); - if (SerializerId != 0) hash ^= SerializerId.GetHashCode(); - if (MessageManifest.Length != 0) hash ^= MessageManifest.GetHashCode(); - return hash; - } + if (SequenceNr != 0L) + { + size += 1 + pb::CodedOutputStream.ComputeInt64Size(SequenceNr); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } + if (WriterGuid.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeStringSize(WriterGuid); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (Message.Length != 0) { - output.WriteRawTag(10); - output.WriteBytes(Message); - } - if (SerializerId != 0) { - output.WriteRawTag(16); - output.WriteInt32(SerializerId); - } - if (MessageManifest.Length != 0) { - output.WriteRawTag(26); - output.WriteBytes(MessageManifest); - } - } + if (payload_ != null) + { + size += 1 + pb::CodedOutputStream.ComputeMessageSize(Payload); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (Message.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeBytesSize(Message); - } - if (SerializerId != 0) { - size += 1 + pb::CodedOutputStream.ComputeInt32Size(SerializerId); - } - if (MessageManifest.Length != 0) { - size += 1 + pb::CodedOutputStream.ComputeBytesSize(MessageManifest); - } - return size; - } + return size; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(PersistentPayload other) { - if (other == null) { - return; - } - if (other.Message.Length != 0) { - Message = other.Message; - } - if (other.SerializerId != 0) { - SerializerId = other.SerializerId; - } - if (other.MessageManifest.Length != 0) { - MessageManifest = other.MessageManifest; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(PersistentMessage other) + { + if (other == null) + { + return; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - input.SkipLastField(); - break; - case 10: { - Message = input.ReadBytes(); - break; - } - case 16: { - SerializerId = input.ReadInt32(); - break; - } - case 26: { - MessageManifest = input.ReadBytes(); - break; - } - } - } - } + if (other.PersistenceId.Length != 0) + { + PersistenceId = other.PersistenceId; + } - } + if (other.SequenceNr != 0L) + { + SequenceNr = other.SequenceNr; + } - public sealed partial class Stored : pb::IMessage { - private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Stored()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pb::MessageParser Parser { get { return _parser; } } + if (other.WriterGuid.Length != 0) + { + WriterGuid = other.WriterGuid; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public static pbr::MessageDescriptor Descriptor { - get { return global::CustomSerialization.Protobuf.Msg.MessageFormatsReflection.Descriptor.MessageTypes[2]; } - } + if (other.payload_ != null) + { + if (payload_ == null) + { + payload_ = new global::CustomSerialization.Protobuf.Msg.PersistentPayload(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - pbr::MessageDescriptor pb::IMessage.Descriptor { - get { return Descriptor; } - } + Payload.MergeFrom(other.Payload); + } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Stored() { - OnConstruction(); + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + PersistenceId = input.ReadString(); + break; + } + case 16: + { + SequenceNr = input.ReadInt64(); + break; + } + case 26: + { + WriterGuid = input.ReadString(); + break; + } + case 34: + { + if (payload_ == null) + { + payload_ = new global::CustomSerialization.Protobuf.Msg.PersistentPayload(); + } + + input.ReadMessage(payload_); + break; + } + } + } + } } - partial void OnConstruction(); + public sealed partial class PersistentPayload : pb::IMessage + { + private static readonly pb::MessageParser _parser = + new pb::MessageParser(() => new PersistentPayload()); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Stored(Stored other) : this() { - value_ = other.value_; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser + { + get { return _parser; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public Stored Clone() { - return new Stored(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor + { + get { return global::CustomSerialization.Protobuf.Msg.MessageFormatsReflection.Descriptor.MessageTypes[1]; } + } - /// Field number for the "value" field. - public const int ValueFieldNumber = 1; - private int value_; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int Value { - get { return value_; } - set { - value_ = value; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override bool Equals(object other) { - return Equals(other as Stored); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public PersistentPayload() + { + OnConstruction(); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public bool Equals(Stored other) { - if (ReferenceEquals(other, null)) { - return false; - } - if (ReferenceEquals(other, this)) { - return true; - } - if (Value != other.Value) return false; - return true; - } + partial void OnConstruction(); - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override int GetHashCode() { - int hash = 1; - if (Value != 0) hash ^= Value.GetHashCode(); - return hash; - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public PersistentPayload(PersistentPayload other) : this() + { + message_ = other.message_; + serializerId_ = other.serializerId_; + messageManifest_ = other.messageManifest_; + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public override string ToString() { - return pb::JsonFormatter.ToDiagnosticString(this); - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public PersistentPayload Clone() + { + return new PersistentPayload(this); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void WriteTo(pb::CodedOutputStream output) { - if (Value != 0) { - output.WriteRawTag(8); - output.WriteInt32(Value); - } - } + /// Field number for the "message" field. + public const int MessageFieldNumber = 1; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public int CalculateSize() { - int size = 0; - if (Value != 0) { - size += 1 + pb::CodedOutputStream.ComputeInt32Size(Value); - } - return size; - } + private pb::ByteString message_ = pb::ByteString.Empty; - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(Stored other) { - if (other == null) { - return; - } - if (other.Value != 0) { - Value = other.Value; - } - } + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString Message + { + get { return message_; } + set { message_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); } + } + + /// Field number for the "serializerId" field. + public const int SerializerIdFieldNumber = 2; + + private int serializerId_; + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int SerializerId + { + get { return serializerId_; } + set { serializerId_ = value; } + } + + /// Field number for the "messageManifest" field. + public const int MessageManifestFieldNumber = 3; + + private pb::ByteString messageManifest_ = pb::ByteString.Empty; + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public pb::ByteString MessageManifest + { + get { return messageManifest_; } + set { messageManifest_ = pb::ProtoPreconditions.CheckNotNull(value, "value"); } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) + { + return Equals(other as PersistentPayload); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(PersistentPayload other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + + if (ReferenceEquals(other, this)) + { + return true; + } + + if (Message != other.Message) return false; + if (SerializerId != other.SerializerId) return false; + if (MessageManifest != other.MessageManifest) return false; + return true; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() + { + int hash = 1; + if (Message.Length != 0) hash ^= Message.GetHashCode(); + if (SerializerId != 0) hash ^= SerializerId.GetHashCode(); + if (MessageManifest.Length != 0) hash ^= MessageManifest.GetHashCode(); + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) + { + if (Message.Length != 0) + { + output.WriteRawTag(10); + output.WriteBytes(Message); + } - [global::System.Diagnostics.DebuggerNonUserCodeAttribute] - public void MergeFrom(pb::CodedInputStream input) { - uint tag; - while ((tag = input.ReadTag()) != 0) { - switch(tag) { - default: - input.SkipLastField(); - break; - case 8: { - Value = input.ReadInt32(); - break; - } - } - } + if (SerializerId != 0) + { + output.WriteRawTag(16); + output.WriteInt32(SerializerId); + } + + if (MessageManifest.Length != 0) + { + output.WriteRawTag(26); + output.WriteBytes(MessageManifest); + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() + { + int size = 0; + if (Message.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(Message); + } + + if (SerializerId != 0) + { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(SerializerId); + } + + if (MessageManifest.Length != 0) + { + size += 1 + pb::CodedOutputStream.ComputeBytesSize(MessageManifest); + } + + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(PersistentPayload other) + { + if (other == null) + { + return; + } + + if (other.Message.Length != 0) + { + Message = other.Message; + } + + if (other.SerializerId != 0) + { + SerializerId = other.SerializerId; + } + + if (other.MessageManifest.Length != 0) + { + MessageManifest = other.MessageManifest; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 10: + { + Message = input.ReadBytes(); + break; + } + case 16: + { + SerializerId = input.ReadInt32(); + break; + } + case 26: + { + MessageManifest = input.ReadBytes(); + break; + } + } + } + } } - } + public sealed partial class Stored : pb::IMessage + { + private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Stored()); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pb::MessageParser Parser + { + get { return _parser; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public static pbr::MessageDescriptor Descriptor + { + get { return global::CustomSerialization.Protobuf.Msg.MessageFormatsReflection.Descriptor.MessageTypes[2]; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + pbr::MessageDescriptor pb::IMessage.Descriptor + { + get { return Descriptor; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Stored() + { + OnConstruction(); + } + + partial void OnConstruction(); + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Stored(Stored other) : this() + { + value_ = other.value_; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public Stored Clone() + { + return new Stored(this); + } + + /// Field number for the "value" field. + public const int ValueFieldNumber = 1; + + private int value_; + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int Value + { + get { return value_; } + set { value_ = value; } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override bool Equals(object other) + { + return Equals(other as Stored); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public bool Equals(Stored other) + { + if (ReferenceEquals(other, null)) + { + return false; + } + + if (ReferenceEquals(other, this)) + { + return true; + } + + if (Value != other.Value) return false; + return true; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override int GetHashCode() + { + int hash = 1; + if (Value != 0) hash ^= Value.GetHashCode(); + return hash; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public override string ToString() + { + return pb::JsonFormatter.ToDiagnosticString(this); + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void WriteTo(pb::CodedOutputStream output) + { + if (Value != 0) + { + output.WriteRawTag(8); + output.WriteInt32(Value); + } + } - #endregion + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public int CalculateSize() + { + int size = 0; + if (Value != 0) + { + size += 1 + pb::CodedOutputStream.ComputeInt32Size(Value); + } + return size; + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(Stored other) + { + if (other == null) + { + return; + } + + if (other.Value != 0) + { + Value = other.Value; + } + } + + [global::System.Diagnostics.DebuggerNonUserCodeAttribute] + public void MergeFrom(pb::CodedInputStream input) + { + uint tag; + while ((tag = input.ReadTag()) != 0) + { + switch (tag) + { + default: + input.SkipLastField(); + break; + case 8: + { + Value = input.ReadInt32(); + break; + } + } + } + } + } + + #endregion } -#endregion Designer generated code +#endregion Designer generated code \ No newline at end of file diff --git a/src/examples/CustomSerialization.Protobuf/Serialization/ProtobufSerializer.cs b/src/examples/CustomSerialization.Protobuf/Serialization/ProtobufSerializer.cs index 29e20f0..02e9050 100644 --- a/src/examples/CustomSerialization.Protobuf/Serialization/ProtobufSerializer.cs +++ b/src/examples/CustomSerialization.Protobuf/Serialization/ProtobufSerializer.cs @@ -1,3 +1,9 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2013-2021 .NET Foundation +// +// ----------------------------------------------------------------------- + using Akka.Actor; using Akka.Serialization; using System; @@ -12,9 +18,8 @@ public class ProtobufSerializer : Serializer { public ProtobufSerializer(ExtendedActorSystem system) : base(system) { - } - + public override int Identifier => 110; public override bool IncludeManifest => true; @@ -35,21 +40,16 @@ public override byte[] ToBinary(object obj) public override object FromBinary(byte[] bytes, Type type) { if (type == typeof(Persistent) || type == typeof(IPersistentRepresentation)) - { return PersistentFromProto(bytes); - } - else if (type == typeof(Stored)) - { - return StoredFromProto(bytes); - } + else if (type == typeof(Stored)) return StoredFromProto(bytes); throw new SerializationException($"Can't serialize object of type {type}"); } // IPersistentRepresentation - private CustomSerialization.Protobuf.Msg.PersistentMessage PersistentToProto(IPersistentRepresentation p) + private Msg.PersistentMessage PersistentToProto(IPersistentRepresentation p) { - var message = new CustomSerialization.Protobuf.Msg.PersistentMessage(); + var message = new Msg.PersistentMessage(); message.PersistenceId = p.PersistenceId; message.SequenceNr = p.SequenceNr; @@ -61,12 +61,12 @@ private CustomSerialization.Protobuf.Msg.PersistentMessage PersistentToProto(IPe private IPersistentRepresentation PersistentFromProto(byte[] bytes) { - var persistentMessage = CustomSerialization.Protobuf.Msg.PersistentMessage.Parser.ParseFrom(bytes); + var persistentMessage = Msg.PersistentMessage.Parser.ParseFrom(bytes); return new Persistent( - payload: PayloadFromProto(persistentMessage.Payload), - sequenceNr: persistentMessage.SequenceNr, - persistenceId: persistentMessage.PersistenceId, + PayloadFromProto(persistentMessage.Payload), + persistentMessage.SequenceNr, + persistentMessage.PersistenceId, writerGuid: persistentMessage.WriterGuid); } @@ -80,14 +80,14 @@ private CustomSerialization.Protobuf.Msg.Stored StoredToProto(Stored stored) private object StoredFromProto(byte[] bytes) { - var persistentMessage = CustomSerialization.Protobuf.Msg.Stored.Parser.ParseFrom(bytes); + var persistentMessage = Msg.Stored.Parser.ParseFrom(bytes); return new Stored(persistentMessage.Value); } // PersistentPayload - private CustomSerialization.Protobuf.Msg.PersistentPayload PersistentPayloadBuilder(object payload) + private Msg.PersistentPayload PersistentPayloadBuilder(object payload) { - var persistentPayload = new CustomSerialization.Protobuf.Msg.PersistentPayload(); + var persistentPayload = new Msg.PersistentPayload(); if (payload == null) return persistentPayload; @@ -97,16 +97,12 @@ private CustomSerialization.Protobuf.Msg.PersistentPayload PersistentPayloadBuil { var manifest = serializerManifest.Manifest(payload); if (!manifest.Equals(Persistent.Undefined)) - { persistentPayload.MessageManifest = ByteString.CopyFromUtf8(manifest); - } } else { if (serializer.IncludeManifest) - { persistentPayload.MessageManifest = ByteString.CopyFromUtf8(payload.GetType().TypeQualifiedName()); - } } persistentPayload.Message = ByteString.CopyFrom(serializer.ToBinary(payload)); @@ -114,7 +110,7 @@ private CustomSerialization.Protobuf.Msg.PersistentPayload PersistentPayloadBuil return persistentPayload; } - private object PayloadFromProto(CustomSerialization.Protobuf.Msg.PersistentPayload persistentPayload) + private object PayloadFromProto(Msg.PersistentPayload persistentPayload) { return system.Serialization.Deserialize( persistentPayload.Message.ToByteArray(), @@ -122,4 +118,4 @@ private object PayloadFromProto(CustomSerialization.Protobuf.Msg.PersistentPaylo persistentPayload.MessageManifest.ToStringUtf8()); } } -} +} \ No newline at end of file